federails 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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