federails 0.0.1 → 0.2.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +182 -7
  3. data/Rakefile +5 -5
  4. data/app/controllers/federails/application_controller.rb +23 -0
  5. data/app/controllers/federails/client/activities_controller.rb +21 -0
  6. data/app/controllers/federails/client/actors_controller.rb +37 -0
  7. data/app/controllers/federails/client/followings_controller.rb +101 -0
  8. data/app/controllers/federails/server/activities_controller.rb +65 -0
  9. data/app/controllers/federails/server/actors_controller.rb +34 -0
  10. data/app/controllers/federails/server/followings_controller.rb +19 -0
  11. data/app/controllers/federails/server/nodeinfo_controller.rb +22 -0
  12. data/app/controllers/federails/server/server_controller.rb +17 -0
  13. data/app/controllers/federails/server/web_finger_controller.rb +38 -0
  14. data/app/helpers/federails/application_helper.rb +8 -0
  15. data/app/jobs/federails/notify_inbox_job.rb +12 -0
  16. data/app/mailers/federails/application_mailer.rb +2 -2
  17. data/app/models/concerns/federails/entity.rb +57 -0
  18. data/app/models/concerns/federails/has_uuid.rb +35 -0
  19. data/app/models/federails/activity.rb +35 -0
  20. data/app/models/federails/actor.rb +189 -0
  21. data/app/models/federails/following.rb +52 -0
  22. data/app/policies/federails/client/activity_policy.rb +6 -0
  23. data/app/policies/federails/client/actor_policy.rb +15 -0
  24. data/app/policies/federails/client/following_policy.rb +35 -0
  25. data/app/policies/federails/federails_policy.rb +59 -0
  26. data/app/policies/federails/server/activity_policy.rb +6 -0
  27. data/app/policies/federails/server/actor_policy.rb +23 -0
  28. data/app/policies/federails/server/following_policy.rb +6 -0
  29. data/app/views/federails/client/activities/_activity.html.erb +5 -0
  30. data/app/views/federails/client/activities/_activity.json.jbuilder +1 -0
  31. data/app/views/federails/client/activities/_index.json.jbuilder +1 -0
  32. data/app/views/federails/client/activities/feed.html.erb +4 -0
  33. data/app/views/federails/client/activities/feed.json.jbuilder +1 -0
  34. data/app/views/federails/client/activities/index.html.erb +5 -0
  35. data/app/views/federails/client/activities/index.json.jbuilder +1 -0
  36. data/app/views/federails/client/actors/_actor.json.jbuilder +14 -0
  37. data/app/views/federails/client/actors/_lookup_form.html.erb +5 -0
  38. data/app/views/federails/client/actors/index.html.erb +24 -0
  39. data/app/views/federails/client/actors/index.json.jbuilder +1 -0
  40. data/app/views/federails/client/actors/show.html.erb +100 -0
  41. data/app/views/federails/client/actors/show.json.jbuilder +1 -0
  42. data/app/views/federails/client/followings/_follow.html.erb +4 -0
  43. data/app/views/federails/client/followings/_follower.html.erb +7 -0
  44. data/app/views/federails/client/followings/_following.json.jbuilder +1 -0
  45. data/app/views/federails/client/followings/_form.html.erb +21 -0
  46. data/app/views/federails/client/followings/index.html.erb +29 -0
  47. data/app/views/federails/client/followings/index.json.jbuilder +1 -0
  48. data/app/views/federails/client/followings/show.html.erb +21 -0
  49. data/app/views/federails/client/followings/show.json.jbuilder +1 -0
  50. data/app/views/federails/server/activities/_activity.activitypub.jbuilder +14 -0
  51. data/app/views/federails/server/activities/outbox.activitypub.jbuilder +18 -0
  52. data/app/views/federails/server/activities/show.activitypub.jbuilder +1 -0
  53. data/app/views/federails/server/actors/_actor.activitypub.jbuilder +21 -0
  54. data/app/views/federails/server/actors/followers.activitypub.jbuilder +18 -0
  55. data/app/views/federails/server/actors/following.activitypub.jbuilder +18 -0
  56. data/app/views/federails/server/actors/show.activitypub.jbuilder +1 -0
  57. data/app/views/federails/server/followings/_following.activitypub.jbuilder +7 -0
  58. data/app/views/federails/server/followings/show.activitypub.jbuilder +1 -0
  59. data/app/views/federails/server/nodeinfo/index.nodeinfo.jbuilder +6 -0
  60. data/app/views/federails/server/nodeinfo/show.nodeinfo.jbuilder +19 -0
  61. data/app/views/federails/server/web_finger/find.jrd.jbuilder +24 -0
  62. data/app/views/federails/server/web_finger/host_meta.xrd.erb +5 -0
  63. data/config/initializers/mime_types.rb +21 -0
  64. data/config/routes.rb +43 -0
  65. data/db/migrate/20200712133150_create_federails_actors.rb +24 -0
  66. data/db/migrate/20200712143127_create_federails_followings.rb +14 -0
  67. data/db/migrate/20200712174938_create_federails_activities.rb +11 -0
  68. data/db/migrate/20240731145400_change_actor_entity_rel_to_polymorphic.rb +11 -0
  69. data/db/migrate/20241002094500_add_uuids.rb +13 -0
  70. data/db/migrate/20241002094501_add_keypair_to_actors.rb +8 -0
  71. data/lib/federails/configuration.rb +92 -0
  72. data/lib/federails/engine.rb +6 -0
  73. data/lib/federails/utils/host.rb +54 -0
  74. data/lib/federails/version.rb +1 -1
  75. data/lib/federails.rb +34 -3
  76. data/lib/fediverse/inbox.rb +71 -0
  77. data/lib/fediverse/notifier.rb +60 -0
  78. data/lib/fediverse/request.rb +38 -0
  79. data/lib/fediverse/signature.rb +49 -0
  80. data/lib/fediverse/webfinger.rb +117 -0
  81. data/lib/generators/federails/install/USAGE +9 -0
  82. data/lib/generators/federails/install/install_generator.rb +10 -0
  83. data/lib/generators/federails/install/templates/federails.rb +1 -0
  84. data/lib/generators/federails/install/templates/federails.yml +23 -0
  85. data/lib/tasks/factory_bot.rake +15 -0
  86. metadata +170 -10
  87. data/app/views/layouts/federails/application.html.erb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b172418ad3d12834c3c724244a83fe3f30115923c3182608d670d164af0f23c3
4
- data.tar.gz: 3e401d421423152b900733c0ad2729b0f2087532e20e416ee69c58393e378ad9
3
+ metadata.gz: 070da5d2cc3d016475a69f797c6280595618d5f60de95cf7506bb2a1e9cbce4c
4
+ data.tar.gz: a954535a092fa71fdb911f973363d8372b6d56e42b32190394e92026567c6913
5
5
  SHA512:
6
- metadata.gz: e32dfc49219c5719c2ccdb56db44c6bbbcc026374cbf8b3bb9e0545e0f925397988b242407e2b57c4e597a7aca2796d88ce3573c3ba66c85933da9dc6ddb319c
7
- data.tar.gz: f98b93107cbf37b63ebd26d128fc4a1f9b9b929f12054406fc4c2e9ea416ce72f2edb273c59e2ac58241cf36782872b81b83311939b054b64ec79c24d4919c1d
6
+ metadata.gz: 69fba1438beef26a150079c2a39eb185903025dcad3c24f5dfee941d844710d12938cf5546f0dc488f725096a2c4e6f32dfba4d1d3c825b9ba9418f063bb4ec9
7
+ data.tar.gz: 01ef6821f7b1aa9422f013e23ed20b1225f0e630d4925ca81c7490f43d790a1d77a3b93e8d72783d93aee59f1760b48bbdd71a59feca353bca0a1277a6bd03c0
data/README.md CHANGED
@@ -1,10 +1,30 @@
1
1
  # Federails
2
- Short description and motivation.
3
2
 
4
- ## Usage
5
- How to use my plugin.
3
+ Federails is an engine that brings ActivityPub to Ruby on Rails application.
4
+
5
+ ## Community
6
+
7
+ You can join the [matrix chat room](https://matrix.to/#/#federails:matrix.org) to chat with humans.
8
+
9
+ Open issues or feature requests on the [issue tracker](https://gitlab.com/experimentslabs/federails/-/issues)
10
+
11
+ ## Features
12
+
13
+ This engine is meant to be used in Rails applications to add the ability to act as an ActivityPub server.
14
+
15
+ As the project is in an early stage of development we're unable to provide a clean list of what works and what is missing.
16
+
17
+ The general direction is to be able to:
18
+
19
+ - publish and subscribe to any type of content
20
+ - have a discovery endpoint (`webfinger`)
21
+ - have a following/followers system
22
+ - implement all the parts of the (RFC) labelled with **MUST** and **MUST NOT**
23
+ - implement some or all the parts of the RFC labelled with **SHOULD** and **SHOULD NOT**
24
+ - maybe implement the parts of the RFC labelled with **MAY**
6
25
 
7
26
  ## Installation
27
+
8
28
  Add this line to your application's Gemfile:
9
29
 
10
30
  ```ruby
@@ -12,17 +32,172 @@ gem "federails"
12
32
  ```
13
33
 
14
34
  And then execute:
35
+
15
36
  ```bash
16
37
  $ bundle
17
38
  ```
18
39
 
19
- Or install it yourself as:
20
- ```bash
21
- $ gem install federails
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
+ ```
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
+ ```
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::Entity
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).actor
149
+
150
+ actor.inbox
151
+ actor.outbox
152
+ actor.followers
153
+ actor.following
154
+ #...
22
155
  ```
23
156
 
24
157
  ## Contributing
25
- Contribution directions go here.
158
+
159
+ Contributions are welcome, may it be issues, ideas, code or whatever you want to share. Please note:
160
+
161
+ - This project is _fast forward_ only: we don't do merge commits
162
+ - We adhere to [semantic versioning](). Please update the changelog in your commits
163
+ - We try to adhere to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) principles
164
+ - We _may_ rename your commits before merging them
165
+ - We _may_ split your commits before merging them
166
+
167
+ To contribute:
168
+
169
+ 1. Fork this repository
170
+ 2. Create small commits
171
+ 3. Ideally create small pull requests. Don't hesitate to open them early so we all can follow how it's going
172
+ 4. Get congratulated
173
+
174
+ ### Tooling
175
+
176
+ #### RSpec
177
+
178
+ RSpec is the test suite. Start it with
179
+
180
+ ```sh
181
+ bundle exec rspec
182
+ ```
183
+
184
+ #### Rubocop
185
+
186
+ Rubocop is a linter. Start it with
187
+
188
+ ```sh
189
+ bundle exec rubocop
190
+ ```
191
+
192
+ #### FactoryBot
193
+
194
+ FactoryBot is a factory generator used in tests and development.
195
+ A rake task checks the replayability of the factories and traits:
196
+
197
+ ```sh
198
+ bundle exec app:factory_bot:lint
199
+ ```
26
200
 
27
201
  ## License
202
+
28
203
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,8 +1,8 @@
1
- require "bundler/setup"
1
+ require 'bundler/setup'
2
2
 
3
- APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
- load "rails/tasks/engine.rake"
3
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
4
+ load 'rails/tasks/engine.rake'
5
5
 
6
- load "rails/tasks/statistics.rake"
6
+ load 'rails/tasks/statistics.rake'
7
7
 
8
- require "bundler/gem_tasks"
8
+ require 'bundler/gem_tasks'
@@ -1,4 +1,27 @@
1
1
  module Federails
2
2
  class ApplicationController < ActionController::Base
3
+ include Pundit::Authorization
4
+
5
+ rescue_from ActiveRecord::RecordNotFound, with: :error_not_found
6
+
7
+ layout Federails.configuration.app_layout if Federails.configuration.app_layout
8
+
9
+ private
10
+
11
+ def error_fallback(exception, fallback_message, status)
12
+ message = exception&.message || fallback_message
13
+ respond_to do |format|
14
+ format.jrd { head status }
15
+ format.xrd { head status }
16
+ format.activitypub { head status }
17
+ format.nodeinfo { head status }
18
+ format.json { render json: { error: message }, status: status }
19
+ format.html { raise exception }
20
+ end
21
+ end
22
+
23
+ def error_not_found(exception = nil)
24
+ error_fallback(exception, 'Resource not found', :not_found)
25
+ end
3
26
  end
4
27
  end
@@ -0,0 +1,21 @@
1
+ module Federails
2
+ module Client
3
+ class ActivitiesController < Federails::ApplicationController
4
+ before_action :authenticate_user!, only: [:feed]
5
+ # layout 'layouts/application'
6
+
7
+ # GET /app/activities
8
+ # GET /app/activities.json
9
+ def index
10
+ @activities = policy_scope(Federails::Activity, policy_scope_class: Federails::Client::ActivityPolicy::Scope).all
11
+ @activities = @activities.where actor: Actor.find_param(params[:actor_id]) if params[:actor_id]
12
+ end
13
+
14
+ # GET /app/feed
15
+ # GET /app/feed.json
16
+ def feed
17
+ @activities = Activity.feed_for(current_user.actor)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ module Federails
2
+ module Client
3
+ class ActorsController < Federails::ApplicationController
4
+ before_action :set_actor, only: [:show]
5
+
6
+ # GET /app/actors
7
+ # GET /app/actors.json
8
+ def index
9
+ @actors = policy_scope(Federails::Actor, policy_scope_class: Federails::Client::ActorPolicy::Scope).all
10
+ end
11
+
12
+ # GET /app/actors/1
13
+ # GET /app/actors/1.json
14
+ def show; end
15
+
16
+ # GET /app/explorer/lookup
17
+ # GET /app/explorer/lookup.json
18
+ def lookup
19
+ @actor = Federails::Actor.find_by_account account_param
20
+ authorize @actor, policy_class: Federails::Client::ActorPolicy
21
+ render :show
22
+ end
23
+
24
+ private
25
+
26
+ # Use callbacks to share common setup or constraints between actions.
27
+ def set_actor
28
+ @actor = Federails::Actor.find_param(params[:id])
29
+ authorize @actor, policy_class: Federails::Client::ActorPolicy
30
+ end
31
+
32
+ def account_param
33
+ params.require('account')
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,101 @@
1
+ module Federails
2
+ module Client
3
+ class FollowingsController < Federails::ApplicationController
4
+ before_action :authenticate_user!
5
+ before_action :set_following, only: [:accept, :destroy]
6
+
7
+ # GET /app/followings/new?uri={uri}
8
+ def new
9
+ # Find actor (and fetch if necessary)
10
+ actor = Actor.find_or_create_by_federation_url(params[:uri])
11
+ # Redirect to local profile page which will have a follow button on it
12
+ redirect_to federails.client_actor_url(actor)
13
+ end
14
+
15
+ # PUT /app/followings/:id/accept
16
+ # PUT /app/followings/:id/accept.json
17
+ def accept
18
+ respond_to do |format|
19
+ url = federails.client_actor_url @following.actor
20
+ if @following.accept!
21
+ format.html { redirect_to url, notice: I18n.t('controller.followings.accept.success') }
22
+ format.json { render :show, status: :ok, location: @following }
23
+ else
24
+ format.html { redirect_to url, alert: I18n.t('controller.followings.accept.error') }
25
+ format.json { render json: @following.errors, status: :unprocessable_entity }
26
+ end
27
+ end
28
+ end
29
+
30
+ # POST /app/followings
31
+ # POST /app/followings.json
32
+ def create
33
+ @following = Following.new(following_params)
34
+ @following.actor = current_user.actor
35
+ authorize @following, policy_class: Federails::Client::FollowingPolicy
36
+
37
+ save_and_render
38
+ end
39
+
40
+ # POST /app/followings/follow
41
+ # POST /app/followings/follow.json
42
+ def follow
43
+ begin
44
+ @following = Following.new_from_account following_account_params, actor: current_user.actor
45
+ authorize @following, policy_class: Federails::Client::FollowingPolicy
46
+ rescue ::ActiveRecord::RecordNotFound
47
+ # Renders a 422 instead of a 404
48
+ respond_to do |format|
49
+ format.html { redirect_to federails.client_actors_url, alert: I18n.t('controller.followings.follow.error') }
50
+ format.json { render json: { target_actor: ['does not exist'] }, status: :unprocessable_entity }
51
+ end
52
+
53
+ return
54
+ end
55
+
56
+ save_and_render
57
+ end
58
+
59
+ # DELETE /app/followings/1
60
+ # DELETE /app/followings/1.json
61
+ def destroy
62
+ @following.destroy
63
+ respond_to do |format|
64
+ format.html { redirect_to federails.client_actor_url(@following.actor), notice: I18n.t('controller.followings.destroy.success') }
65
+ format.json { head :no_content }
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ # Use callbacks to share common setup or constraints between actions.
72
+ def set_following
73
+ @following = Following.find_param(params[:id])
74
+ authorize @following, policy_class: Federails::Client::FollowingPolicy
75
+ end
76
+
77
+ # Only allow a list of trusted parameters through.
78
+ def following_params
79
+ params.require(:following).permit(:target_actor_id)
80
+ end
81
+
82
+ def following_account_params
83
+ params.require(:account)
84
+ end
85
+
86
+ def save_and_render # rubocop:disable Metrics/AbcSize
87
+ url = federails.client_actor_url current_user.actor
88
+
89
+ respond_to do |format|
90
+ if @following.save
91
+ format.html { redirect_to url, notice: I18n.t('controller.followings.save_and_render.success') }
92
+ format.json { render :show, status: :created, location: @following }
93
+ else
94
+ format.html { redirect_to url, alert: I18n.t('controller.followings.save_and_render.error') }
95
+ format.json { render json: @following.errors, status: :unprocessable_entity }
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,65 @@
1
+ require 'fediverse/inbox'
2
+
3
+ module Federails
4
+ module Server
5
+ class ActivitiesController < ServerController
6
+ before_action :set_activity, only: [:show]
7
+
8
+ # GET /federation/activities
9
+ # GET /federation/actors/1/outbox.json
10
+ def outbox
11
+ @actor = Actor.find_param(params[:actor_id])
12
+ @activities = policy_scope(Federails::Activity, policy_scope_class: Federails::Server::ActivityPolicy::Scope).where(actor: @actor).order(created_at: :desc)
13
+ @total_activities = @activities.count
14
+ @activities = @activities.page(params[:page])
15
+ end
16
+
17
+ # GET /federation/actors/1/activities/1.json
18
+ def show; end
19
+
20
+ # POST /federation/actors/1/inbox
21
+ def create
22
+ payload = payload_from_params
23
+ return head :unprocessable_entity unless payload
24
+
25
+ if Fediverse::Inbox.dispatch_request(payload)
26
+ head :created
27
+ else
28
+ head :unprocessable_entity
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # Use callbacks to share common setup or constraints between actions.
35
+ def set_activity
36
+ @activity = Actor.find_param(params[:actor_id]).activities.find_param(params[:id])
37
+ end
38
+
39
+ # Only allow a list of trusted parameters through.
40
+ def activity_params
41
+ params.fetch(:activity, {})
42
+ end
43
+
44
+ def payload_from_params
45
+ payload_string = request.body.read
46
+ request.body.rewind if request.body.respond_to? :rewind
47
+
48
+ begin
49
+ payload = JSON.parse(payload_string)
50
+ rescue JSON::ParserError
51
+ return
52
+ end
53
+
54
+ hash = JSON::LD::API.compact payload, payload['@context']
55
+ validate_payload hash
56
+ end
57
+
58
+ def validate_payload(hash)
59
+ return unless hash['@context'] && hash['id'] && hash['type'] && hash['actor'] && hash['object']
60
+
61
+ hash
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,34 @@
1
+ module Federails
2
+ module Server
3
+ class ActorsController < ServerController
4
+ before_action :set_actor, only: [:show, :followers, :following]
5
+
6
+ # GET /federation/actors/1
7
+ # GET /federation/actors/1.json
8
+ def show; end
9
+
10
+ def followers
11
+ @actors = @actor.followers.order(created_at: :desc)
12
+ followings_queries
13
+ end
14
+
15
+ def following
16
+ @actors = @actor.follows.order(created_at: :desc)
17
+ followings_queries
18
+ end
19
+
20
+ private
21
+
22
+ # Use callbacks to share common setup or constraints between actions.
23
+ def set_actor
24
+ @actor = Actor.find_param(params[:id])
25
+ authorize @actor, policy_class: Federails::Server::ActorPolicy
26
+ end
27
+
28
+ def followings_queries
29
+ @total_actors = @actors.count
30
+ @actors = @actors.page(params[:page])
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ module Federails
2
+ module Server
3
+ class FollowingsController < ServerController
4
+ before_action :set_following, only: [:show]
5
+
6
+ # GET /federation/actors/1/followings/1.json
7
+ def show; end
8
+
9
+ private
10
+
11
+ # Use callbacks to share common setup or constraints between actions.
12
+ def set_following
13
+ actor = Actor.find_param(params[:actor_id])
14
+ @following = Following.find_by!(actor: actor, uuid: params[:id])
15
+ authorize @following, policy_class: Federails::Server::FollowingPolicy
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module Federails
2
+ module Server
3
+ class NodeinfoController < ServerController
4
+ def index
5
+ render formats: [:nodeinfo]
6
+ end
7
+
8
+ def show # rubocop:todo Metrics/AbcSize
9
+ @total = @active_halfyear = @active_month = 0
10
+ Federails::Configuration.entity_types.each_value do |config|
11
+ next unless config[:include_in_user_count]
12
+
13
+ model = config[:class]
14
+ @total += model.count
15
+ @active_month += model.where(created_at: ((30.days.ago)...Time.current)).count
16
+ @active_halfyear += model.where(created_at: ((180.days.ago)...Time.current)).count
17
+ end
18
+ render formats: [:nodeinfo]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module Federails
2
+ module Server
3
+ class ServerController < Federails::ApplicationController
4
+ protect_from_forgery with: :null_session
5
+
6
+ # def policy_scope(scope, policy_scope_class: nil)
7
+ # scope = [scope, :server] unless policy_scope_class
8
+ # super(scope, policy_scope_class: policy_scope_class)
9
+ # end
10
+
11
+ # def authorize(record, query = nil, policy_class: nil)
12
+ # record = [:server, record] unless policy_class
13
+ # super(record, query, policy_class: policy_class)
14
+ # end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ require 'fediverse/webfinger'
2
+
3
+ module Federails
4
+ module Server
5
+ class WebFingerController < ServerController
6
+ def find
7
+ resource = params.require(:resource)
8
+ case resource
9
+ when %r{^https?://.+}
10
+ @user = Federails::Actor.find_by_federation_url(resource)&.entity
11
+ when /^acct:.+/
12
+ Federails::Configuration.entity_types.each_value do |entity|
13
+ @user ||= entity[:class].find_by(entity[:username_field] => username)
14
+ end
15
+ end
16
+ raise ActiveRecord::RecordNotFound if @user.nil?
17
+
18
+ render formats: [:jrd]
19
+ end
20
+
21
+ def host_meta
22
+ render formats: [:xrd]
23
+ end
24
+
25
+ # TODO: complete missing endpoints
26
+
27
+ private
28
+
29
+ def username
30
+ account = Fediverse::Webfinger.split_resource_account params.require(:resource)
31
+ # Fail early if user don't _seems_ local
32
+ raise ActiveRecord::RecordNotFound unless account && Fediverse::Webfinger.local_user?(account)
33
+
34
+ account[:username]
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,4 +1,12 @@
1
1
  module Federails
2
2
  module ApplicationHelper
3
+ def remote_follow_url
4
+ method_name = Federails.configuration.remote_follow_url_method.to_s
5
+ if method_name.starts_with? 'federails.'
6
+ send(method_name.gsub('federails.', ''))
7
+ else
8
+ Rails.application.routes.url_helpers.send(method_name)
9
+ end
10
+ end
3
11
  end
4
12
  end
@@ -0,0 +1,12 @@
1
+ require 'fediverse/notifier'
2
+
3
+ module Federails
4
+ class NotifyInboxJob < ApplicationJob
5
+ queue_as :default
6
+
7
+ def perform(activity)
8
+ activity.reload
9
+ Fediverse::Notifier.post_to_inboxes(activity)
10
+ end
11
+ end
12
+ end
@@ -1,6 +1,6 @@
1
1
  module Federails
2
2
  class ApplicationMailer < ActionMailer::Base
3
- default from: "from@example.com"
4
- layout "mailer"
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
5
  end
6
6
  end