federails 0.0.1 → 0.1.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +171 -7
  3. data/Rakefile +5 -5
  4. data/app/controllers/federails/application_controller.rb +19 -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 +93 -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 +18 -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/jobs/federails/notify_inbox_job.rb +12 -0
  15. data/app/mailers/federails/application_mailer.rb +2 -2
  16. data/app/models/concerns/federails/entity.rb +46 -0
  17. data/app/models/federails/activity.rb +40 -0
  18. data/app/models/federails/actor.rb +152 -0
  19. data/app/models/federails/following.rb +43 -0
  20. data/app/policies/federails/client/activity_policy.rb +6 -0
  21. data/app/policies/federails/client/actor_policy.rb +15 -0
  22. data/app/policies/federails/client/following_policy.rb +35 -0
  23. data/app/policies/federails/federails_policy.rb +59 -0
  24. data/app/policies/federails/server/activity_policy.rb +6 -0
  25. data/app/policies/federails/server/actor_policy.rb +23 -0
  26. data/app/policies/federails/server/following_policy.rb +6 -0
  27. data/app/views/federails/client/activities/_activity.html.erb +5 -0
  28. data/app/views/federails/client/activities/_activity.json.jbuilder +1 -0
  29. data/app/views/federails/client/activities/_index.json.jbuilder +1 -0
  30. data/app/views/federails/client/activities/feed.html.erb +4 -0
  31. data/app/views/federails/client/activities/feed.json.jbuilder +1 -0
  32. data/app/views/federails/client/activities/index.html.erb +5 -0
  33. data/app/views/federails/client/activities/index.json.jbuilder +1 -0
  34. data/app/views/federails/client/actors/_actor.json.jbuilder +14 -0
  35. data/app/views/federails/client/actors/_lookup_form.html.erb +5 -0
  36. data/app/views/federails/client/actors/index.html.erb +24 -0
  37. data/app/views/federails/client/actors/index.json.jbuilder +1 -0
  38. data/app/views/federails/client/actors/show.html.erb +100 -0
  39. data/app/views/federails/client/actors/show.json.jbuilder +1 -0
  40. data/app/views/federails/client/followings/_follow.html.erb +4 -0
  41. data/app/views/federails/client/followings/_follower.html.erb +7 -0
  42. data/app/views/federails/client/followings/_following.json.jbuilder +1 -0
  43. data/app/views/federails/client/followings/_form.html.erb +21 -0
  44. data/app/views/federails/client/followings/index.html.erb +29 -0
  45. data/app/views/federails/client/followings/index.json.jbuilder +1 -0
  46. data/app/views/federails/client/followings/show.html.erb +21 -0
  47. data/app/views/federails/client/followings/show.json.jbuilder +1 -0
  48. data/app/views/federails/server/activities/_activity.json.jbuilder +9 -0
  49. data/app/views/federails/server/activities/outbox.json.jbuilder +18 -0
  50. data/app/views/federails/server/activities/show.json.jbuilder +1 -0
  51. data/app/views/federails/server/actors/_actor.json.jbuilder +11 -0
  52. data/app/views/federails/server/actors/followers.json.jbuilder +18 -0
  53. data/app/views/federails/server/actors/following.json.jbuilder +18 -0
  54. data/app/views/federails/server/actors/show.json.jbuilder +1 -0
  55. data/app/views/federails/server/followings/_following.json.jbuilder +7 -0
  56. data/app/views/federails/server/followings/show.json.jbuilder +1 -0
  57. data/app/views/federails/server/nodeinfo/index.json.jbuilder +6 -0
  58. data/app/views/federails/server/nodeinfo/show.json.jbuilder +19 -0
  59. data/app/views/federails/server/web_finger/find.json.jbuilder +20 -0
  60. data/app/views/federails/server/web_finger/host_meta.xml.erb +5 -0
  61. data/config/routes.rb +43 -0
  62. data/db/migrate/20200712133150_create_federails_actors.rb +24 -0
  63. data/db/migrate/20200712143127_create_federails_followings.rb +14 -0
  64. data/db/migrate/20200712174938_create_federails_activities.rb +11 -0
  65. data/db/migrate/20240731145400_change_actor_entity_rel_to_polymorphic.rb +11 -0
  66. data/lib/federails/configuration.rb +88 -0
  67. data/lib/federails/engine.rb +6 -0
  68. data/lib/federails/utils/host.rb +54 -0
  69. data/lib/federails/version.rb +1 -1
  70. data/lib/federails.rb +33 -3
  71. data/lib/fediverse/inbox.rb +71 -0
  72. data/lib/fediverse/notifier.rb +21 -0
  73. data/lib/fediverse/request.rb +38 -0
  74. data/lib/fediverse/webfinger.rb +93 -0
  75. data/lib/generators/federails/install/USAGE +9 -0
  76. data/lib/generators/federails/install/install_generator.rb +10 -0
  77. data/lib/generators/federails/install/templates/federails.rb +1 -0
  78. data/lib/generators/federails/install/templates/federails.yml +23 -0
  79. data/lib/tasks/factory_bot.rake +15 -0
  80. metadata +165 -10
  81. 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: bc485556cd2ae749668f8f6a6ffec973361bdef6970e421845b9c2de64e8cecf
4
+ data.tar.gz: 34630a4b116e47b3d4c3ec4fd7599ba0264f4dd76fdff5db795d2be90c50c2cc
5
5
  SHA512:
6
- metadata.gz: e32dfc49219c5719c2ccdb56db44c6bbbcc026374cbf8b3bb9e0545e0f925397988b242407e2b57c4e597a7aca2796d88ce3573c3ba66c85933da9dc6ddb319c
7
- data.tar.gz: f98b93107cbf37b63ebd26d128fc4a1f9b9b929f12054406fc4c2e9ea416ce72f2edb273c59e2ac58241cf36782872b81b83311939b054b64ec79c24d4919c1d
6
+ metadata.gz: b296b6db88b1077326722a187f6074fc8f9bbeb4f43d0f90a2c7811fb7bfd14b0e247f64149ce41c0e53128ce45d3e7e61ecfb0da8d47e64b6b7f188dfa33c8c
7
+ data.tar.gz: 948085a88295cf6c6699ed1dbea2a64a30d8695bb1a9d596cb83d66753eb4e26b5129d0d00574d21f230544b75a3f859411e64c785878abc2ce85021ef41a2b5
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,161 @@ 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
+ ### Migrations
106
+
107
+ Copy the migrations:
108
+
109
+ ```sh
110
+ bundle exec rails federails:install:migrations
111
+ ```
112
+
113
+ ### User model
114
+
115
+ In the ActivityPub world, we refer to _actors_ to represent the thing that publishes or subscribe to _other actors_.
116
+
117
+ Federails provides a concern to include in your "user" model or whatever will publish data:
118
+
119
+ ```rb
120
+ # app/models/user.rb
121
+
122
+ class User < ApplicationRecord
123
+ # Include the concern here:
124
+ include Federails::Entity
125
+
126
+ # Configure field names
127
+ acts_as_federails_actor username_field: :username, name_field: :name, profile_url_method: :user_url
128
+ end
129
+ ```
130
+
131
+ This concern automatically create a `Federails::Actor` after a user creation, as well as the `actor` reference. When adding it to
132
+ an existing model with existing data, you will need to generate the corresponding actors yourself in a migration.
133
+
134
+ Usage example:
135
+
136
+ ```rb
137
+ actor = User.find(1).actor
138
+
139
+ actor.inbox
140
+ actor.outbox
141
+ actor.followers
142
+ actor.following
143
+ #...
22
144
  ```
23
145
 
24
146
  ## Contributing
25
- Contribution directions go here.
147
+
148
+ Contributions are welcome, may it be issues, ideas, code or whatever you want to share. Please note:
149
+
150
+ - This project is _fast forward_ only: we don't do merge commits
151
+ - We adhere to [semantic versioning](). Please update the changelog in your commits
152
+ - We try to adhere to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) principles
153
+ - We _may_ rename your commits before merging them
154
+ - We _may_ split your commits before merging them
155
+
156
+ To contribute:
157
+
158
+ 1. Fork this repository
159
+ 2. Create small commits
160
+ 3. Ideally create small pull requests. Don't hesitate to open them early so we all can follow how it's going
161
+ 4. Get congratulated
162
+
163
+ ### Tooling
164
+
165
+ #### RSpec
166
+
167
+ RSpec is the test suite. Start it with
168
+
169
+ ```sh
170
+ bundle exec rspec
171
+ ```
172
+
173
+ #### Rubocop
174
+
175
+ Rubocop is a linter. Start it with
176
+
177
+ ```sh
178
+ bundle exec rubocop
179
+ ```
180
+
181
+ #### FactoryBot
182
+
183
+ FactoryBot is a factory generator used in tests and development.
184
+ A rake task checks the replayability of the factories and traits:
185
+
186
+ ```sh
187
+ bundle exec app:factory_bot:lint
188
+ ```
26
189
 
27
190
  ## License
191
+
28
192
  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,23 @@
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.json { render json: { error: message }, status: status }
15
+ format.html { raise exception }
16
+ end
17
+ end
18
+
19
+ def error_not_found(exception = nil)
20
+ error_fallback(exception, 'Resource not found', :not_found)
21
+ end
3
22
  end
4
23
  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_id: 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(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,93 @@
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
+ # PUT /app/followings/:id/accept
8
+ # PUT /app/followings/:id/accept.json
9
+ def accept
10
+ respond_to do |format|
11
+ url = federails.client_actor_url @following.actor
12
+ if @following.accept!
13
+ format.html { redirect_to url, notice: I18n.t('controller.followings.accept.success') }
14
+ format.json { render :show, status: :ok, location: @following }
15
+ else
16
+ format.html { redirect_to url, alert: I18n.t('controller.followings.accept.error') }
17
+ format.json { render json: @following.errors, status: :unprocessable_entity }
18
+ end
19
+ end
20
+ end
21
+
22
+ # POST /app/followings
23
+ # POST /app/followings.json
24
+ def create
25
+ @following = Following.new(following_params)
26
+ @following.actor = current_user.actor
27
+ authorize @following, policy_class: Federails::Client::FollowingPolicy
28
+
29
+ save_and_render
30
+ end
31
+
32
+ # POST /app/followings/follow
33
+ # POST /app/followings/follow.json
34
+ def follow
35
+ begin
36
+ @following = Following.new_from_account following_account_params, actor: current_user.actor
37
+ authorize @following, policy_class: Federails::Client::FollowingPolicy
38
+ rescue ::ActiveRecord::RecordNotFound
39
+ # Renders a 422 instead of a 404
40
+ respond_to do |format|
41
+ format.html { redirect_to federails.client_actors_url, alert: I18n.t('controller.followings.follow.error') }
42
+ format.json { render json: { target_actor: ['does not exist'] }, status: :unprocessable_entity }
43
+ end
44
+
45
+ return
46
+ end
47
+
48
+ save_and_render
49
+ end
50
+
51
+ # DELETE /app/followings/1
52
+ # DELETE /app/followings/1.json
53
+ def destroy
54
+ @following.destroy
55
+ respond_to do |format|
56
+ format.html { redirect_to federails.client_actor_url(@following.actor), notice: I18n.t('controller.followings.destroy.success') }
57
+ format.json { head :no_content }
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ # Use callbacks to share common setup or constraints between actions.
64
+ def set_following
65
+ @following = Following.find(params[:id])
66
+ authorize @following, policy_class: Federails::Client::FollowingPolicy
67
+ end
68
+
69
+ # Only allow a list of trusted parameters through.
70
+ def following_params
71
+ params.require(:following).permit(:target_actor_id)
72
+ end
73
+
74
+ def following_account_params
75
+ params.require(:account)
76
+ end
77
+
78
+ def save_and_render # rubocop:disable Metrics/AbcSize
79
+ url = federails.client_actor_url current_user.actor
80
+
81
+ respond_to do |format|
82
+ if @following.save
83
+ format.html { redirect_to url, notice: I18n.t('controller.followings.save_and_render.success') }
84
+ format.json { render :show, status: :created, location: @following }
85
+ else
86
+ format.html { redirect_to url, alert: I18n.t('controller.followings.save_and_render.error') }
87
+ format.json { render json: @following.errors, status: :unprocessable_entity }
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ 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(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 render json: {}, status: :unprocessable_entity unless payload
24
+
25
+ if Fediverse::Inbox.dispatch_request(payload)
26
+ render json: {}, status: :created
27
+ else
28
+ render json: {}, status: :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 = Activity.find_by!(actor_id: params[:actor_id], id: 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(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,18 @@
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
+ @following = Following.find_by!(actor_id: params[:actor_id], id: params[:id])
14
+ authorize @following, policy_class: Federails::Server::FollowingPolicy
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ module Federails
2
+ module Server
3
+ class NodeinfoController < ServerController
4
+ def index
5
+ render formats: [:json]
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: [:json]
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: [:json]
19
+ end
20
+
21
+ def host_meta
22
+ render content_type: 'application/xrd+xml', formats: [:xml]
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
@@ -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
@@ -0,0 +1,46 @@
1
+ module Federails
2
+ module Entity
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_one :actor, class_name: 'Federails::Actor', as: :entity, dependent: :destroy
7
+
8
+ after_create :create_actor
9
+
10
+ # Configures the mapping between entity and actor
11
+ # @param username_field [Symbol] The method or attribute name that returns the preferred username for ActivityPub
12
+ # @param name_field [Symbol] The method or attribute name that returns the preferred name for ActivityPub
13
+ # @param profile_url_method [Symbol] The route method name that will generate the profile URL for ActivityPub
14
+ # @param actor_type [String] The ActivityStreams Actor type for this entity; defaults to 'Person'
15
+ # @param include_in_user_count [boolean] Should this entity be included in the nodeinfo user count? Defaults to true
16
+ # @example
17
+ # acts_as_federails_actor username_field: :username, name_field: :display_name, profile_url_method: :url_for, actor_type: 'Person'
18
+ def self.acts_as_federails_actor(
19
+ username_field: Federails::Configuration.user_username_field,
20
+ name_field: Federails::Configuration.user_name_field,
21
+ profile_url_method: Federails.configuration.user_profile_url_method,
22
+ actor_type: 'Person',
23
+ include_in_user_count: true
24
+ )
25
+ Federails::Configuration.register_entity(
26
+ self,
27
+ username_field: username_field,
28
+ name_field: name_field,
29
+ profile_url_method: profile_url_method,
30
+ actor_type: actor_type,
31
+ include_in_user_count: include_in_user_count
32
+ )
33
+ end
34
+
35
+ # Automatically run default acts_as_federails_actor
36
+ # this can be optionally called again with different configuration in the entity
37
+ acts_as_federails_actor
38
+
39
+ private
40
+
41
+ def create_actor
42
+ Federails::Actor.create! entity: self
43
+ end
44
+ end
45
+ end
46
+ end