federails 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a3463b1a877ca8224ade011016236c1fdbcbe41e31712b609347f41592d30531
4
- data.tar.gz: 16ec7975b26bc5fbec1e243a4653c27d8b0239c7dbda42544150c9e5680d65f5
3
+ metadata.gz: 9e6d475999161590317e4a038b44829fdae2a44ccb007b03f24c240fccfa0771
4
+ data.tar.gz: b2266e7c4e9ac8c68177e2b17fc2e74afab52fc0e93607943716e06b39c10f6d
5
5
  SHA512:
6
- metadata.gz: b54850967223bb1be7aadd54547f757a9e5fbe6ccc8d84249a4a2b340a494b275905c25e862c493b2d2b177a7cc5e12abf2d6e44c4f84b4bec76796a7b9cba5f
7
- data.tar.gz: cc00b05153d4434eacc6409a4422575d5e851e07c332001a72fc818e5f64d1bf6f2cd35f6eaa5f4025b3150fe9009a370665a044d44f84a60399690186a8f5e8
6
+ metadata.gz: 14c482129bac20d5dfdaa2fa498070ab215819a0fe6af6499e268d631f40a7c15ee3341e999e6b0bfee5fd997b7a41f879c35537aeebbfbbdfc6182289d5edf4
7
+ data.tar.gz: 0c6fe55888c7b31f93958e258ad4b137aa305269056b2b512af2a137e2a15ac6de36a0a83f7fbec436c6657fca3c4ad9c18c08bb46a93e48601ef226c9a0011c
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,206 +23,11 @@ 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
+ ## Documentation
27
27
 
28
- Add this line to your application's Gemfile:
29
-
30
- ```ruby
31
- gem "federails"
32
- ```
33
-
34
- And then execute:
35
-
36
- ```bash
37
- $ bundle
38
- ```
39
-
40
- ### Configuration
41
-
42
- Generate configuration files:
43
-
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**
180
-
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.
182
-
183
- ## Contributing
184
-
185
- Contributions are welcome, may it be issues, ideas, code or whatever you want to share. Please note:
186
-
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
192
-
193
- To contribute:
194
-
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
199
-
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
- ```
28
+ - [Usage](docs/usage.md)
29
+ - [Common questions](docs/faq.md)
30
+ - [Contributing](docs/contributing.md)
226
31
 
227
32
  ## License
228
33
 
@@ -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_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
@@ -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,72 @@ 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
+ #
64
+ # @param method_name [Symbol] The name of the method to call, or a block that will be called directly
65
+ #
72
66
  # @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
- {}
67
+ # after_followed :accept_follow
68
+ def after_followed(method_name)
69
+ set_callback :followed, :after, method_name
89
70
  end
90
71
 
91
- private
92
-
93
- def create_federails_actor
94
- Federails::Actor.create! entity: self
72
+ # Define a method that will be called after an activity has been received
73
+ #
74
+ # @param activity_type [String] The activity action to handle, e.g. 'Create'. If you specify '*', the handler will be called for any activity type.
75
+ # @param object_type [String] The object type to handle, e.g. 'Note'. If you specify '*', the handler will be called for any object type.
76
+ # @param method_name [Symbol] The name of the class method to call. The method will receive the complete activity payload as a parameter.
77
+ #
78
+ # @example
79
+ # after_activity_received 'Create', 'Note', :create_note
80
+ def after_activity_received(activity_type, object_type, method_name)
81
+ Fediverse::Inbox.register_handler(activity_type, object_type, self, method_name)
95
82
  end
96
83
  end
84
+
85
+ included do
86
+ include ActiveSupport::Callbacks
87
+
88
+ define_callbacks :followed
89
+
90
+ has_one :federails_actor, class_name: 'Federails::Actor', as: :entity, dependent: :destroy
91
+
92
+ after_create :create_federails_actor, if: lambda {
93
+ raise("Entity not configured for #{self.class.name}. Did you use \"acts_as_federails_actor\"?") unless Federails.actor_entity? self
94
+
95
+ Federails.actor_entity(self)[:auto_create_actors]
96
+ }
97
+ end
98
+
99
+ # Add custom data to actor responses.
100
+ #
101
+ # Override in your own model to add extra data, which will be merged into the actor response
102
+ # generated by Federails. You can include extra `@context` for activitypub extensions and it will
103
+ # be merged with the main response context.
104
+ #
105
+ # @example
106
+ # def to_activitypub_object
107
+ # {
108
+ # "@context": {
109
+ # toot: "http://joinmastodon.org/ns#",
110
+ # attributionDomains: {
111
+ # "@id": "toot:attributionDomains",
112
+ # "@type": "@id"
113
+ # }
114
+ # },
115
+ # attributionDomains: [
116
+ # "example.com"
117
+ # ]
118
+ # }
119
+ # end
120
+ def to_activitypub_object
121
+ {}
122
+ end
123
+
124
+ private
125
+
126
+ def create_federails_actor
127
+ Federails::Actor.create! entity: self
128
+ end
97
129
  end
98
130
  end
@@ -0,0 +1,178 @@
1
+ require 'fediverse/inbox'
2
+
3
+ module Federails
4
+ # Model concern to include in models for which data is pushed to the Fediverse and comes from the Fediverse.
5
+ #
6
+ # Once included, an activity will automatically be created upon
7
+ # - entity creation
8
+ # - entity updates
9
+ #
10
+ # Also, when properly configured, a handler is registered to transform incoming objects and create/update entities
11
+ # accordingly.
12
+ #
13
+ # ## Pre-requisites
14
+ #
15
+ # Model must have a `federated_url` attribute:
16
+ # ```rb
17
+ # add_column :posts, :federated_url, :string, null: true, default: nil
18
+ # ```
19
+ #
20
+ # ## Usage
21
+ #
22
+ # Include the concern in an existing model:
23
+ #
24
+ # ```rb
25
+ # class Post < ApplicationRecord
26
+ # include Federails::DataEntity
27
+ # acts_as_federails_data options
28
+ # end
29
+ # ```
30
+ module DataEntity
31
+ extend ActiveSupport::Concern
32
+
33
+ # Class methods automatically included in the concern.
34
+ module ClassMethods
35
+ # Configures the mapping between entity and Fediverse
36
+ #
37
+ # Model should have the following methods:
38
+ # - `to_activitypub_object`, returning a valid ActivityPub object
39
+ #
40
+ # @param actor_entity_method [Symbol] Method returning an object responding to 'federails_actor', for local content
41
+ # @param url_param [Symbol] Column name of the object ID that should be used in URLs. Defaults to +:id+
42
+ # @param route_path_segment [Symbol] Segment used in Federails routes to display the ActivityPub representation.
43
+ # Defaults to the pluralized, underscored class name
44
+ # @param handles [String] Type of ActivityPub object handled by this entity type
45
+ # @param with [Symbol] Self class method that will handle incoming objects. Defaults to +:handle_incoming_fediverse_data+
46
+ # @param filter_method [Symbol] Self class method that determines if an incoming object should be handled. Note
47
+ # that the first model for which this method returns true will be used. If left empty, the model CAN be selected,
48
+ # so define them if many models handle the same data type.
49
+ # @param should_federate_method [Symbol] method to determine if an object should be federated. If the method returns false,
50
+ # no create/update activities will happen, and object will not be accessible at federated_url. Defaults to a method
51
+ # that always returns true.
52
+ #
53
+ # @example
54
+ # acts_as_federails_data handles: 'Note', with: :note_handler, route_path_segment: :articles, actor_entity_method: :user
55
+ # rubocop:disable Metrics/ParameterLists
56
+ def acts_as_federails_data(
57
+ handles:,
58
+ with: :handle_incoming_fediverse_data,
59
+ route_path_segment: nil,
60
+ actor_entity_method: nil,
61
+ url_param: :id,
62
+ filter_method: nil,
63
+ should_federate_method: :default_should_federate?
64
+ )
65
+ route_path_segment ||= name.pluralize.underscore
66
+
67
+ Federails::Configuration.register_data_type self,
68
+ route_path_segment: route_path_segment,
69
+ actor_entity_method: actor_entity_method,
70
+ url_param: url_param,
71
+ handles: handles,
72
+ with: with,
73
+ filter_method: filter_method,
74
+ should_federate_method: should_federate_method
75
+
76
+ Fediverse::Inbox.register_handler 'Create', handles, self, with
77
+ Fediverse::Inbox.register_handler 'Update', handles, self, with
78
+ end
79
+ # rubocop:enable Metrics/ParameterLists
80
+
81
+ # Instantiates a new instance from an ActivityPub object
82
+ #
83
+ # @param activitypub_object [Hash]
84
+ #
85
+ # @return [self]
86
+ def new_from_activitypub_object(activitypub_object)
87
+ new from_activitypub_object(activitypub_object)
88
+ end
89
+
90
+ # Creates or updates entity based on the ActivityPub activity
91
+ #
92
+ # @param activity_hash_or_id [Hash, String] Dereferenced activity hash or ID
93
+ #
94
+ # @return [self]
95
+ def handle_incoming_fediverse_data(activity_hash_or_id)
96
+ activity = Fediverse::Request.dereference(activity_hash_or_id)
97
+ object = Fediverse::Request.dereference(activity['object'])
98
+
99
+ entity = Federails::Utils::Object.find_or_create!(object)
100
+
101
+ if activity['type'] == 'Update'
102
+ entity.assign_attributes from_activitypub_object(object)
103
+
104
+ # Use timestamps from attributes
105
+ entity.save! touch: false
106
+ end
107
+
108
+ entity
109
+ end
110
+ end
111
+
112
+ included do
113
+ belongs_to :federails_actor, class_name: 'Federails::Actor'
114
+
115
+ scope :local_federails_entities, -> { where federated_url: nil }
116
+ scope :distant_federails_entities, -> { where.not(federated_url: nil) }
117
+
118
+ before_validation :set_federails_actor
119
+ after_create :create_federails_activity
120
+ after_update :update_federails_activity
121
+ end
122
+
123
+ # Computed value for the federated URL
124
+ #
125
+ # @return [String]
126
+ def federated_url
127
+ return nil unless send(federails_data_configuration[:should_federate_method])
128
+ return attributes['federated_url'] if attributes['federated_url'].present?
129
+
130
+ path_segment = Federails.data_entity_configuration(self)[:route_path_segment]
131
+ url_param = Federails.data_entity_configuration(self)[:url_param]
132
+ Federails::Engine.routes.url_helpers.server_published_url(publishable_type: path_segment, id: send(url_param))
133
+ end
134
+
135
+ # Check whether the entity was created locally or comes from the Fediverse
136
+ #
137
+ # @return [Boolean]
138
+ def local_federails_entity?
139
+ attributes['federated_url'].blank?
140
+ end
141
+
142
+ def federails_data_configuration
143
+ Federails.data_entity_configuration(self)
144
+ end
145
+
146
+ private
147
+
148
+ def set_federails_actor
149
+ return federails_actor if federails_actor.present?
150
+
151
+ self.federails_actor = send(federails_data_configuration[:actor_entity_method])&.federails_actor if federails_data_configuration[:actor_entity_method]
152
+
153
+ raise 'Cannot determine actor from configuration' unless federails_actor
154
+ end
155
+
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
164
+ ensure_federails_configuration!
165
+ return unless local_federails_entity? && send(federails_data_configuration[:should_federate_method])
166
+
167
+ Activity.create! actor: federails_actor, action: 'Update', entity: self
168
+ end
169
+
170
+ def ensure_federails_configuration!
171
+ raise("Entity not configured for #{self.class.name}. Did you use \"acts_as_federails_data\"?") unless Federails.data_entity? self
172
+ end
173
+
174
+ def default_should_federate?
175
+ true
176
+ end
177
+ end
178
+ end
@@ -1,4 +1,28 @@
1
1
  module Federails
2
+ # Model concern providing UUIDs as model parameter (instead of IDs).
3
+ #
4
+ #
5
+ # A _required_, `uuid` field is required on the model's table for this concern to work:
6
+ #
7
+ # ```rb
8
+ # # Example migration
9
+ # add_column :my_table, :uuid, :text, default: nil, index: { unique: true }
10
+ # ```
11
+ #
12
+ # Usage:
13
+ #
14
+ # ```rb
15
+ # class MyModel < ApplicationRecord
16
+ # include Federails::HasUuid
17
+ # end
18
+ #
19
+ # # And now:
20
+ # instance = MyModel.find_param 'aaaa_bbbb_cccc_dddd_....'
21
+ # instance.to_param
22
+ # # => 'aaaa_bbbb_cccc_dddd_....'
23
+ # ```
24
+ #
25
+ # It can be added on existing tables without data migration as the `uuid` accessor will generate the value when missing.
2
26
  module HasUuid
3
27
  extend ActiveSupport::Concern
4
28
 
@@ -11,12 +35,14 @@ module Federails
11
35
  end
12
36
  end
13
37
 
38
+ # @return [String] The UUID
14
39
  def to_param
15
40
  uuid
16
41
  end
17
42
 
18
- # Override UUID accessor to provide lazy initialization of UUIDs for old data
43
+ # @return [String]
19
44
  def uuid
45
+ # Override UUID accessor to provide lazy initialization of UUIDs for old data
20
46
  if self[:uuid].blank?
21
47
  generate_uuid
22
48
  save!
@@ -1,4 +1,13 @@
1
1
  module Federails
2
+ # Activities can be compared to a log of what happened in the Fediverse.
3
+ #
4
+ # Activities from local actors ends in the actors _outboxes_.
5
+ # Activities form distant actors comes from the actor's _inbox_.
6
+ # We try to only keep activities _from_ local actors, and external activities _targetting_ local actors.
7
+ #
8
+ # See also:
9
+ # - https://www.w3.org/TR/activitypub/#outbox
10
+ # - https://www.w3.org/TR/activitypub/#inbox
2
11
  class Activity < ApplicationRecord
3
12
  include Federails::HasUuid
4
13
 
@@ -15,6 +24,9 @@ module Federails
15
24
 
16
25
  after_create_commit :post_to_inboxes
17
26
 
27
+ # Determines the list of actors targeted by the activity
28
+ #
29
+ # @return [Array<Federails::Actor>]
18
30
  def recipients
19
31
  return [] unless actor.local?
20
32
 
@@ -2,6 +2,12 @@ require 'federails/utils/host'
2
2
  require 'fediverse/webfinger'
3
3
 
4
4
  module Federails
5
+ # Model storing _distant_ actors and links to local ones.
6
+ #
7
+ # To make a model act as an actor, use the `Federails::ActorEntity` concern
8
+ #
9
+ # See also:
10
+ # - https://www.w3.org/TR/activitypub/#actor-objects
5
11
  class Actor < ApplicationRecord # rubocop:disable Metrics/ClassLength
6
12
  include Federails::HasUuid
7
13
 
@@ -25,6 +31,7 @@ module Federails
25
31
  has_many :follows, source: :target_actor, through: :following_follows
26
32
 
27
33
  scope :local, -> { where.not(entity: nil) }
34
+ scope :distant, -> { where.not(federated_url: nil) }
28
35
 
29
36
  def local?
30
37
  entity.present?
@@ -1,4 +1,5 @@
1
1
  module Federails
2
+ # Stores following data between actors
2
3
  class Following < ApplicationRecord
3
4
  include Federails::HasUuid
4
5
 
@@ -0,0 +1,15 @@
1
+ module Federails
2
+ module Server
3
+ class PublishablePolicy < Federails::FederailsPolicy
4
+ def show?
5
+ @record.send(@record.federails_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,11 @@
1
+ context = true unless context == false
2
+ json.set! '@context', 'https://www.w3.org/ns/activitystreams' if context
3
+
4
+ publishable.to_activitypub_object.each_pair do |key, value|
5
+ json.set! key, value
6
+ end
7
+
8
+ json.id publishable.federated_url
9
+ json.actor publishable.federails_actor.federated_url
10
+ json.to ['https://www.w3.org/ns/activitystreams#Public']
11
+ json.cc [publishable.federails_actor.followers_url]
@@ -0,0 +1 @@
1
+ json.partial! 'federails/server/published/publishable', publishable: @publishable
data/config/routes.rb CHANGED
@@ -45,5 +45,9 @@ Federails::Engine.routes.draw do
45
45
  resources :activities, only: [:show]
46
46
  resources :followings, only: [:show]
47
47
  end
48
+
49
+ scope :published do
50
+ get ':publishable_type/:id', to: 'published#show', as: :published
51
+ end
48
52
  end
49
53
  end
@@ -1,5 +1,7 @@
1
1
  module Federails
2
2
  # rubocop:disable Style/ClassVars
3
+
4
+ # Stores the Federails configuration in a _singleton_.
3
5
  module Configuration
4
6
  # Application name, used in well-known and nodeinfo endpoints
5
7
  mattr_accessor :app_name
@@ -45,7 +47,16 @@ module Federails
45
47
  mattr_accessor :base_client_controller
46
48
  @@base_client_controller = 'ActionController::Base'
47
49
 
50
+ # @!method self.remote_follow_url_method
51
+ #
48
52
  # Route method for remote-following requests
53
+
54
+ # @!method self.remote_follow_url_method=(value)
55
+ #
56
+ # Sets the route method for remote-following requests
57
+ # @param value [String] Route method name as used in links
58
+ # @example
59
+ # remote_follow_url_method 'main_app.my_custom_route_helper'
49
60
  mattr_accessor :remote_follow_url_method
50
61
  @@remote_follow_url_method = 'federails.new_client_following_url'
51
62
 
@@ -66,6 +77,14 @@ module Federails
66
77
  def self.register_actor_class(klass, config = {})
67
78
  @@actor_types[klass.name] = config.merge(class: klass)
68
79
  end
80
+
81
+ # List of data types (classes using Federails::DataEntity)
82
+ mattr_reader :data_types
83
+ @@data_types = {}
84
+
85
+ def self.register_data_type(klass, config = {})
86
+ @@data_types[klass.name] = config.merge(class: klass)
87
+ end
69
88
  end
70
89
  # rubocop:enable Style/ClassVars
71
90
  end
@@ -0,0 +1,31 @@
1
+ module Federails
2
+ module DataTransformer
3
+ module Note
4
+ # Renders a Note. The entity is used to determine actor and generic fields data
5
+ #
6
+ # @param entity [#federail_actor] A model instance
7
+ # @param content [String] Note content
8
+ # @param name [String, nil] Optional name/title
9
+ # @param custom [Hash] Optional additional keys (e.g.: attachment, icon, ...). Defaults will override these.
10
+ #
11
+ # @return [Hash]
12
+ #
13
+ # @example
14
+ # Federails::DataTransformer::Note.to_federation(comment, content: comment.content, custom: { 'inReplyTo' => comment.parent.federated_url })
15
+ #
16
+ # See:
17
+ # - https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object
18
+ # - https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note
19
+ def self.to_federation(entity, content:, name: nil, custom: {})
20
+ custom.merge '@context' => 'https://www.w3.org/ns/activitystreams',
21
+ 'id' => entity.federated_url,
22
+ 'type' => 'Note',
23
+ 'name' => name,
24
+ 'content' => content,
25
+ 'attributedTo' => entity.federails_actor.federated_url,
26
+ 'published' => entity.created_at,
27
+ 'updated' => entity.updated_at
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,106 @@
1
+ module Federails
2
+ module Utils
3
+ # Methods to manipulate incoming objects
4
+ class Object
5
+ class << self
6
+ # Finds data from an object or its ID.
7
+ #
8
+ # When data exists locally, the entity is returned.
9
+ # For distant data, a new instance is returned unless the target does not exist.
10
+ #
11
+ # @param object_or_id [String, Hash] String identifier or incoming object
12
+ #
13
+ # @return [ApplicationRecord, nil] Entity or nil when invalid/not found
14
+ def find_or_initialize(object_or_id)
15
+ federated_url = object_or_id.is_a?(Hash) ? object_or_id['id'] : object_or_id
16
+
17
+ route = local_route(federated_url)
18
+ return from_local_route(route) if route
19
+
20
+ from_distant_server(object_or_id)
21
+ end
22
+
23
+ # Finds or initializes an entity from an ActivityPub object or id
24
+ #
25
+ # @see .find_or_initialize
26
+ #
27
+ # @param object_or_id [String, Hash] String identifier or incoming object
28
+ #
29
+ # @return [ApplicationRecord, nil] Entity or nil when invalid/not found
30
+ def find_or_initialize!(object_or_id)
31
+ entity = find_or_initialize object_or_id
32
+ raise ActiveRecord::RecordNotFound unless entity
33
+
34
+ entity
35
+ end
36
+
37
+ # Finds or create an entity from an ActivityPub object or id
38
+ #
39
+ # Note that the data transformer MUST return timestamps from the ActivityPub object if used on the model,
40
+ # as they won't be set automatically.
41
+ #
42
+ # @see .find_or_initialize!
43
+ #
44
+ # @param object_or_id [String, Hash] String identifier or incoming object
45
+ #
46
+ # @return [ApplicationRecord, nil] Entity or nil when invalid/not found
47
+ def find_or_create!(object_or_id)
48
+ entity = find_or_initialize! object_or_id
49
+ return entity if entity.persisted?
50
+
51
+ entity.save!(touch: false)
52
+ entity
53
+ end
54
+
55
+ # Returns the timestamps to use from an ActivityPub object
56
+ #
57
+ # @param hash [Hash] ActivityPub object
58
+ #
59
+ # @return [Hash] Hash with timestamps
60
+ def timestamp_attributes(hash)
61
+ {
62
+ created_at: hash['published'] ||= Time.current,
63
+ updated_at: hash['updated'].presence || hash['published'],
64
+ }
65
+ end
66
+
67
+ private
68
+
69
+ def local_route(url)
70
+ route = Utils::Host.local_route(url)
71
+
72
+ return nil unless route && route[:controller] == 'federails/server/published' && route[:action] == 'show'
73
+
74
+ route
75
+ end
76
+
77
+ def from_local_route(route)
78
+ config = Federails.data_entity_handled_on route[:publishable_type]
79
+ return unless config
80
+
81
+ config[:class]&.find_by(config[:url_param] => route[:id])
82
+ rescue ActiveRecord::RecordNotFound
83
+ nil
84
+ end
85
+
86
+ def from_distant_server(federated_url)
87
+ hash = Fediverse::Request.dereference(federated_url)
88
+ return unless hash
89
+
90
+ handler = Federails.data_entity_handler_for hash
91
+ return unless handler
92
+
93
+ entity = handler[:class].find_by federated_url: hash['id']
94
+ return entity if entity
95
+
96
+ entity = handler[:class].new_from_activitypub_object(hash)
97
+ return unless entity
98
+
99
+ entity.federails_actor = Federails::Actor.find_or_create_by_object hash['attributedTo'] if entity && !entity.federails_actor
100
+
101
+ entity
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -1,3 +1,3 @@
1
1
  module Federails
2
- VERSION = '0.4.0'.freeze
2
+ VERSION = '0.5.0'.freeze
3
3
  end
data/lib/federails.rb CHANGED
@@ -1,9 +1,14 @@
1
1
  require 'federails/version'
2
2
  require 'federails/engine'
3
3
  require 'federails/configuration'
4
+ require 'federails/utils/object'
4
5
 
5
6
  # rubocop:disable Style/ClassVars
7
+
8
+ # This module includes classes and methods related to Ruby on Rails: engine configuration, models, controllers, etc.
6
9
  module Federails
10
+ DEFAULT_DATA_FILTER_METHOD = :handle_federated_object?
11
+
7
12
  mattr_reader :configuration
8
13
  @@configuration = Configuration
9
14
 
@@ -49,6 +54,55 @@ module Federails
49
54
  Configuration.actor_types[klass]
50
55
  end
51
56
 
57
+ # @return [Boolean] True if the given model is a possible data entity
58
+ def data_entity?(class_or_instance)
59
+ Configuration.data_types.key? class_or_instance_name(class_or_instance)
60
+ end
61
+
62
+ # Finds configured data types from ActivityPub type
63
+ #
64
+ # @param type [String] ActivityPub object type, as configured with `:handles`
65
+ # @return [Array] List of data entity configurations
66
+ #
67
+ # @example
68
+ # data_entity_handlers_for 'Note'
69
+ def data_entity_handlers_for(type)
70
+ Federails::Configuration.data_types.select { |_, v| v[:handles] == type }.map(&:last)
71
+ end
72
+
73
+ # Finds the configured handler for a given ActivityPub object
74
+ #
75
+ # @param hash [Hash] ActivityPub object hash
76
+ #
77
+ # @return [Hash, nil] Data entity configuration
78
+ def data_entity_handler_for(hash)
79
+ data_entity_handlers_for(hash['type']).find do |handler|
80
+ return true if !handler[:filter_method] && !handler[:class].respond_to?(DEFAULT_DATA_FILTER_METHOD)
81
+
82
+ handler[:class].send(handler[:filter_method] || DEFAULT_DATA_FILTER_METHOD, hash)
83
+ end
84
+ end
85
+
86
+ # Finds configured data type from route path segment
87
+ #
88
+ # @param route_path_segment [Symbol, String] Route path segment, as configured with `:route_path_segment`
89
+ # @return [Hash, nil] Entity configuration
90
+ #
91
+ # @example
92
+ # data_entity_handled_on :articles
93
+ def data_entity_handled_on(route_path_segment)
94
+ route_path_segment = route_path_segment.to_sym
95
+ Federails::Configuration.data_types.find { |_, v| v[:route_path_segment] == route_path_segment }&.last
96
+ end
97
+
98
+ # @return [Hash] The configuration for the given data entity
99
+ def data_entity_configuration(class_or_instance)
100
+ klass = class_or_instance_name(class_or_instance)
101
+ raise "#{klass} is not a configured data entity" unless Configuration.data_types.key?(klass)
102
+
103
+ Configuration.data_types[klass]
104
+ end
105
+
52
106
  private
53
107
 
54
108
  # @return [String] Class name of the provided class or instance
@@ -4,21 +4,32 @@ module Fediverse
4
4
  class Inbox
5
5
  @@handlers = {} # rubocop:todo Style/ClassVars
6
6
  class << self
7
+ # Registers a handler for incoming data
8
+ #
9
+ # @param activity_type [String] Target activity type ('Create', 'Follow', 'Like', ...)
10
+ # See https://www.w3.org/TR/activitystreams-vocabulary/#activity-types for a list of common ones
11
+ # @param object_type [String] Type of the related object ('Article', 'Note', ...)
12
+ # See https://www.w3.org/TR/activitystreams-vocabulary/#object-types for a list of common object types
13
+ # @param klass [String] Class handling the incoming object
14
+ # @param method [Symbol] Method in the class that will handle the object
7
15
  def register_handler(activity_type, object_type, klass, method)
8
16
  @@handlers[activity_type] ||= {}
9
17
  @@handlers[activity_type][object_type] ||= {}
10
18
  @@handlers[activity_type][object_type][klass] = method
11
19
  end
12
20
 
21
+ # Executes the registered handler for an incoming object
22
+ #
23
+ # @param payload [Hash] Dereferenced activity
13
24
  def dispatch_request(payload)
14
- handlers = get_handlers(payload['type'], payload['object'].is_a?(Hash) ? payload.dig('object', 'type') : nil)
25
+ payload['object'] = Fediverse::Request.dereference(payload['object']) if payload.key? 'object'
26
+
27
+ handlers = get_handlers(payload['type'], payload.dig('object', 'type'))
15
28
  handlers.each_pair do |klass, method|
16
29
  klass.send method, payload
17
30
  end
18
31
  return true unless handlers.empty?
19
32
 
20
- # FIXME: Fails silently
21
- # raise NotImplementedError
22
33
  Rails.logger.debug { "Unhandled activity type: #{payload['type']}" }
23
34
  false
24
35
  end
@@ -40,7 +51,7 @@ module Fediverse
40
51
  end
41
52
 
42
53
  def handle_accept_request(activity)
43
- original_activity = Request.get(activity['object'])
54
+ original_activity = Request.dereference(activity['object'])
44
55
 
45
56
  actor = Federails::Actor.find_or_create_by_object original_activity['actor']
46
57
  target_actor = Federails::Actor.find_or_create_by_object original_activity['object']
@@ -3,6 +3,9 @@ require 'fediverse/signature'
3
3
  module Fediverse
4
4
  class Notifier
5
5
  class << self
6
+ # Posts an activity to its recipients
7
+ #
8
+ # @param activity [Federails::Activity]
6
9
  def post_to_inboxes(activity)
7
10
  actors = activity.recipients
8
11
  Rails.logger.debug('Nobody to notice') && return if actors.count.zero?
@@ -11,6 +11,7 @@ module Fediverse
11
11
  @id = id
12
12
  end
13
13
 
14
+ # FIXME: Replace by `Webfinger.get_json` (move other method here as class method)
14
15
  def get
15
16
  Rails.logger.debug { "GET #{@id}" }
16
17
  @response = Faraday.get(@id, nil, BASE_HEADERS)
@@ -21,6 +22,18 @@ module Fediverse
21
22
  def get(id)
22
23
  new(id).get
23
24
  end
25
+
26
+ # Dereferences a value
27
+ #
28
+ # @param value [String, Hash]
29
+ #
30
+ # @return [Hash, nil]
31
+ def dereference(value)
32
+ return value if value.is_a? Hash
33
+ return get(value) if value.is_a? String
34
+
35
+ raise "Unhandled object type #{value.class}"
36
+ end
24
37
  end
25
38
 
26
39
  private
@@ -4,31 +4,63 @@ require 'faraday/follow_redirects'
4
4
  require 'federails/utils/host'
5
5
 
6
6
  module Fediverse
7
+ # Methods related to Webfinger: find accounts, fetch actors,...
7
8
  class Webfinger
8
9
  class << self
9
10
  ACCOUNT_REGEX = /(?<username>[a-z0-9\-_.]+)(?:@(?<domain>.*))?/
10
11
 
12
+ # Extracts username and domain from a "acct:username@domain" string
13
+ #
14
+ # @param account [String] Account string
15
+ #
16
+ # @return [MatchData, nil] Matches with +:username+ and +:domain+ or +nil+
11
17
  def split_resource_account(account)
12
18
  /\Aacct:#{ACCOUNT_REGEX}\z/io.match account
13
19
  end
14
20
 
21
+ # Extracts username and domain from a "username@domain" string
22
+ #
23
+ # @param account [String] Account string
24
+ #
25
+ # @return [MatchData, nil] Matches with +:username+ and +:domain+ or +nil+
15
26
  def split_account(account)
16
27
  /\A#{ACCOUNT_REGEX}\z/io.match account
17
28
  end
18
29
 
19
- def local_user?(account)
20
- account[:username] && (account[:domain].nil? || (account[:domain] == Federails::Utils::Host.localhost))
30
+ # Determines if a given account string should be a local account (same host as configured one)
31
+ #
32
+ # @param hash [Hash, MatchData] Object with +:username+ and +:domain+ keys
33
+ #
34
+ # @return [Boolean]
35
+ def local_user?(hash)
36
+ hash[:username] && (hash[:domain].nil? || (hash[:domain] == Federails::Utils::Host.localhost))
21
37
  end
22
38
 
39
+ # Fetches a distant actor
40
+ #
41
+ # @param username [String]
42
+ # @param domain [String]
43
+ #
44
+ # @return [Federails::Actor, nil] Federails actor or nothing when not found
23
45
  def fetch_actor(username, domain)
24
46
  fetch_actor_url webfinger(username, domain)
25
47
  end
26
48
 
49
+ # Fetches an actor given its URL
50
+ #
51
+ # @param url [String] Actor's federation URL
52
+ #
53
+ # @return [Federails::Actor, nil] Federails actor or nothing when not found
27
54
  def fetch_actor_url(url)
28
55
  webfinger_to_actor get_json url
29
56
  end
30
57
 
31
- # Returns actor id
58
+ # Gets the real actor's federation URL from its username and domain
59
+ #
60
+ # @param username [String]
61
+ # @param domain [String]
62
+ #
63
+ # @return [String, nil] Federation URL if found
32
64
  def webfinger(username, domain)
33
65
  json = webfinger_response(username, domain)
34
66
  link = json['links'].find { |l| l['type'] == 'application/activity+json' }
@@ -37,6 +69,12 @@ module Fediverse
37
69
  end
38
70
 
39
71
  # Returns remote follow link template, or complete link if actor_url is provided
72
+ #
73
+ # @param username [String]
74
+ # @param domain [String]
75
+ # @param actor_url [String] Optional Federation URL to provide when known
76
+ #
77
+ # @return [String] The URL to use as follow URL
40
78
  def remote_follow_url(username, domain, actor_url: nil)
41
79
  json = webfinger_response(username, domain)
42
80
  link = json['links'].find { |l| l['rel'] == 'http://ostatus.org/schema/1.0/subscribe' }
@@ -51,13 +89,17 @@ module Fediverse
51
89
 
52
90
  private
53
91
 
92
+ # Makes a webfinger request for a given username/domain
93
+ # @return [Hash] Webfinger response's content
54
94
  def webfinger_response(username, domain)
55
95
  scheme = Federails.configuration.force_ssl ? 'https' : 'http'
56
96
  get_json "#{scheme}://#{domain}/.well-known/webfinger", resource: "acct:#{username}@#{domain}"
57
97
  end
58
98
 
59
- def server_and_port(id)
60
- uri = URI.parse id
99
+ # Extracts the server and port from a string, omitting common ports
100
+ # @return [String] Server and port
101
+ def server_and_port(string)
102
+ uri = URI.parse string
61
103
  if uri.port && [80, 443].exclude?(uri.port)
62
104
  "#{uri.host}:#{uri.port}"
63
105
  else
@@ -65,6 +107,9 @@ module Fediverse
65
107
  end
66
108
  end
67
109
 
110
+ # Builds a +Federails::Actor+ from a Webfinger response
111
+ # @param data [Hash] Webfinger response
112
+ # @return [Federails::Actor]
68
113
  def webfinger_to_actor(data)
69
114
  Federails::Actor.new federated_url: data['id'],
70
115
  username: data['preferredUsername'],
@@ -78,6 +123,9 @@ module Fediverse
78
123
  public_key: data.dig('publicKey', 'publicKeyPem')
79
124
  end
80
125
 
126
+ # Makes a simple GET request and returns a +Hash+ from the parsed body
127
+ # @return [Hash]
128
+ # @raise [ActiveRecord::RecordNotFound] when the response is invalid
81
129
  def get_json(url, payload = {})
82
130
  response = get(url, payload: payload, headers: { accept: 'application/json' })
83
131
 
@@ -93,10 +141,12 @@ module Fediverse
93
141
  raise ActiveRecord::RecordNotFound
94
142
  end
95
143
 
96
- # Only perform a GET request and throws an ActiveRecord::RecordNotFound
97
- # on error.
98
- # That's "ok-ish"; when an actor is unavailable, whatever the reason is, it's
99
- # not found...
144
+ # Only perform a GET request and throws an ActiveRecord::RecordNotFound on error.
145
+ #
146
+ # That's "ok-ish"; when an actor is unavailable, whatever the reason is, it's not found...
147
+ #
148
+ # @return [Faraday::Response]
149
+ # @raise [ActiveRecord::RecordNotFound] when the response is invalid
100
150
  def get(url, payload: {}, headers: {})
101
151
  connection = Faraday.new url: url, params: payload, headers: headers do |faraday|
102
152
  faraday.response :follow_redirects # use Faraday::FollowRedirects::Middleware
data/lib/fediverse.rb ADDED
@@ -0,0 +1,3 @@
1
+ # This module includes classes and helpers to interact with the Fediverse.
2
+ module Fediverse
3
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: federails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manuel Tancoigne
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-02 00:00:00.000000000 Z
11
+ date: 2025-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -115,6 +115,7 @@ executables: []
115
115
  extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
+ - LICENSE
118
119
  - README.md
119
120
  - Rakefile
120
121
  - app/assets/config/federails_manifest.js
@@ -127,6 +128,7 @@ files:
127
128
  - app/controllers/federails/server/actors_controller.rb
128
129
  - app/controllers/federails/server/followings_controller.rb
129
130
  - app/controllers/federails/server/nodeinfo_controller.rb
131
+ - app/controllers/federails/server/published_controller.rb
130
132
  - app/controllers/federails/server/web_finger_controller.rb
131
133
  - app/controllers/federails/server_controller.rb
132
134
  - app/helpers/federails/server_helper.rb
@@ -134,6 +136,7 @@ files:
134
136
  - app/jobs/federails/notify_inbox_job.rb
135
137
  - app/mailers/federails/application_mailer.rb
136
138
  - app/models/concerns/federails/actor_entity.rb
139
+ - app/models/concerns/federails/data_entity.rb
137
140
  - app/models/concerns/federails/has_uuid.rb
138
141
  - app/models/federails/activity.rb
139
142
  - app/models/federails/actor.rb
@@ -146,6 +149,7 @@ files:
146
149
  - app/policies/federails/server/activity_policy.rb
147
150
  - app/policies/federails/server/actor_policy.rb
148
151
  - app/policies/federails/server/following_policy.rb
152
+ - app/policies/federails/server/publishable_policy.rb
149
153
  - app/views/federails/client/activities/_activity.html.erb
150
154
  - app/views/federails/client/activities/_activity.json.jbuilder
151
155
  - app/views/federails/client/activities/_index.json.jbuilder
@@ -180,6 +184,8 @@ files:
180
184
  - app/views/federails/server/followings/show.activitypub.jbuilder
181
185
  - app/views/federails/server/nodeinfo/index.nodeinfo.jbuilder
182
186
  - app/views/federails/server/nodeinfo/show.nodeinfo.jbuilder
187
+ - app/views/federails/server/published/_publishable.activitypub.jbuilder
188
+ - app/views/federails/server/published/show.activitypub.jbuilder
183
189
  - app/views/federails/server/web_finger/find.jrd.jbuilder
184
190
  - app/views/federails/server/web_finger/host_meta.xrd.erb
185
191
  - config/initializers/mime_types.rb
@@ -191,9 +197,12 @@ files:
191
197
  - db/migrate/20241002094501_add_keypair_to_actors.rb
192
198
  - lib/federails.rb
193
199
  - lib/federails/configuration.rb
200
+ - lib/federails/data_transformer/note.rb
194
201
  - lib/federails/engine.rb
195
202
  - lib/federails/utils/host.rb
203
+ - lib/federails/utils/object.rb
196
204
  - lib/federails/version.rb
205
+ - lib/fediverse.rb
197
206
  - lib/fediverse/inbox.rb
198
207
  - lib/fediverse/notifier.rb
199
208
  - lib/fediverse/request.rb
@@ -230,7 +239,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
230
239
  - !ruby/object:Gem::Version
231
240
  version: '0'
232
241
  requirements: []
233
- rubygems_version: 3.3.7
242
+ rubygems_version: 3.5.23
234
243
  signing_key:
235
244
  specification_version: 4
236
245
  summary: An ActivityPub engine for Ruby on Rails