fedipub 0.0.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +70 -2
  3. data/Rakefile +5 -9
  4. data/app/assets/config/fedipub_manifest.js +1 -0
  5. data/app/assets/stylesheets/fedipub/application.css +15 -0
  6. data/app/controllers/concerns/fedipub/server/render_collections.rb +19 -0
  7. data/app/controllers/fedipub/client/activities_controller.rb +27 -0
  8. data/app/controllers/fedipub/client/actors_controller.rb +54 -0
  9. data/app/controllers/fedipub/client/followings_controller.rb +103 -0
  10. data/app/controllers/fedipub/client_controller.rb +9 -0
  11. data/app/controllers/fedipub/server/activities_controller.rb +78 -0
  12. data/app/controllers/fedipub/server/actors_controller.rb +50 -0
  13. data/app/controllers/fedipub/server/followings_controller.rb +19 -0
  14. data/app/controllers/fedipub/server/nodeinfo_controller.rb +28 -0
  15. data/app/controllers/fedipub/server/published_controller.rb +30 -0
  16. data/app/controllers/fedipub/server/web_finger_controller.rb +45 -0
  17. data/app/controllers/fedipub/server_controller.rb +37 -0
  18. data/app/helpers/fedipub/server_helper.rb +18 -0
  19. data/app/jobs/fedipub/application_job.rb +10 -0
  20. data/app/jobs/fedipub/fetch_nodeinfo_job.rb +10 -0
  21. data/app/jobs/fedipub/notify_inbox_job.rb +10 -0
  22. data/app/mailers/fedipub/application_mailer.rb +6 -0
  23. data/app/models/concerns/fedipub/actor_entity.rb +166 -0
  24. data/app/models/concerns/fedipub/announceable.rb +18 -0
  25. data/app/models/concerns/fedipub/data_entity.rb +279 -0
  26. data/app/models/concerns/fedipub/handles_delete_requests.rb +36 -0
  27. data/app/models/concerns/fedipub/has_uuid.rb +61 -0
  28. data/app/models/concerns/fedipub/likeable.rb +29 -0
  29. data/app/models/fedipub/activity.rb +62 -0
  30. data/app/models/fedipub/actor.rb +303 -0
  31. data/app/models/fedipub/application_record.rb +5 -0
  32. data/app/models/fedipub/following.rb +72 -0
  33. data/app/models/fedipub/host.rb +48 -0
  34. data/app/policies/fedipub/client/activity_policy.rb +9 -0
  35. data/app/policies/fedipub/client/actor_policy.rb +15 -0
  36. data/app/policies/fedipub/client/following_policy.rb +35 -0
  37. data/app/policies/fedipub/fedipub_policy.rb +63 -0
  38. data/app/policies/fedipub/server/activity_policy.rb +9 -0
  39. data/app/policies/fedipub/server/actor_policy.rb +23 -0
  40. data/app/policies/fedipub/server/following_policy.rb +6 -0
  41. data/app/policies/fedipub/server/publishable_policy.rb +15 -0
  42. data/app/views/fedipub/client/activities/_activity.html.erb +5 -0
  43. data/app/views/fedipub/client/activities/_activity.json.jbuilder +1 -0
  44. data/app/views/fedipub/client/activities/_index.json.jbuilder +1 -0
  45. data/app/views/fedipub/client/activities/feed.html.erb +11 -0
  46. data/app/views/fedipub/client/activities/feed.json.jbuilder +1 -0
  47. data/app/views/fedipub/client/activities/index.html.erb +5 -0
  48. data/app/views/fedipub/client/activities/index.json.jbuilder +1 -0
  49. data/app/views/fedipub/client/actors/_actor.json.jbuilder +17 -0
  50. data/app/views/fedipub/client/actors/_lookup_form.html.erb +5 -0
  51. data/app/views/fedipub/client/actors/gone.html.erb +1 -0
  52. data/app/views/fedipub/client/actors/index.html.erb +44 -0
  53. data/app/views/fedipub/client/actors/index.json.jbuilder +1 -0
  54. data/app/views/fedipub/client/actors/show.html.erb +112 -0
  55. data/app/views/fedipub/client/actors/show.json.jbuilder +1 -0
  56. data/app/views/fedipub/client/common/_client_links.html.erb +26 -0
  57. data/app/views/fedipub/client/followings/_follow.html.erb +4 -0
  58. data/app/views/fedipub/client/followings/_follow_actions.html.erb +36 -0
  59. data/app/views/fedipub/client/followings/_follower.html.erb +7 -0
  60. data/app/views/fedipub/client/followings/_following.json.jbuilder +1 -0
  61. data/app/views/fedipub/client/followings/_form.html.erb +22 -0
  62. data/app/views/fedipub/client/followings/index.html.erb +29 -0
  63. data/app/views/fedipub/client/followings/index.json.jbuilder +1 -0
  64. data/app/views/fedipub/client/followings/show.html.erb +21 -0
  65. data/app/views/fedipub/client/followings/show.json.jbuilder +1 -0
  66. data/app/views/fedipub/server/activities/_activity.activitypub.jbuilder +25 -0
  67. data/app/views/fedipub/server/activities/show.activitypub.jbuilder +1 -0
  68. data/app/views/fedipub/server/actors/_actor.activitypub.jbuilder +27 -0
  69. data/app/views/fedipub/server/actors/_tombstone.activitypub.jbuilder +6 -0
  70. data/app/views/fedipub/server/actors/show.activitypub.jbuilder +5 -0
  71. data/app/views/fedipub/server/followings/_following.activitypub.jbuilder +7 -0
  72. data/app/views/fedipub/server/followings/show.activitypub.jbuilder +1 -0
  73. data/app/views/fedipub/server/nodeinfo/index.nodeinfo.jbuilder +6 -0
  74. data/app/views/fedipub/server/nodeinfo/show.nodeinfo.jbuilder +19 -0
  75. data/app/views/fedipub/server/published/_publishable.activitypub.jbuilder +11 -0
  76. data/app/views/fedipub/server/published/_tombstone.activitypub.jbuilder +6 -0
  77. data/app/views/fedipub/server/published/show.activitypub.jbuilder +5 -0
  78. data/app/views/fedipub/server/shared/ordered_collection.activitypub.jbuilder +6 -0
  79. data/app/views/fedipub/server/shared/ordered_collection_page.activitypub.jbuilder +12 -0
  80. data/app/views/fedipub/server/web_finger/find.jrd.jbuilder +24 -0
  81. data/app/views/fedipub/server/web_finger/host_meta.xrd.erb +5 -0
  82. data/app/views/users/show.html.erb +4 -0
  83. data/config/initializers/mime_types.rb +21 -0
  84. data/config/initializers/security_vocab.rb +53 -0
  85. data/config/routes.rb +53 -0
  86. data/db/migrate/20200712133150_create_federails_actors.rb +22 -0
  87. data/db/migrate/20200712143127_create_federails_followings.rb +14 -0
  88. data/db/migrate/20200712174938_create_federails_activities.rb +11 -0
  89. data/db/migrate/20241002094500_add_uuids.rb +13 -0
  90. data/db/migrate/20241002094501_add_keypair_to_actors.rb +8 -0
  91. data/db/migrate/20250122160618_add_extensions_to_federails_actors.rb +5 -0
  92. data/db/migrate/20250301082500_add_local_to_actors.rb +11 -0
  93. data/db/migrate/20250329123939_add_actor_type_to_actors.rb +5 -0
  94. data/db/migrate/20250329123940_add_tombstoned_at_to_actors.rb +5 -0
  95. data/db/migrate/20250426061729_create_federails_hosts.rb +22 -0
  96. data/db/migrate/20251121160720_add_to_and_cc_to_federails_activities.rb +6 -0
  97. data/db/migrate/20260421170640_add_result_and_instrument_to_federails_activities.rb +6 -0
  98. data/db/migrate/20260723113716_rename_federails_to_fedipub.rb +15 -0
  99. data/lib/fedipub/configuration.rb +119 -0
  100. data/lib/fedipub/data_transformer/note.rb +36 -0
  101. data/lib/fedipub/engine.rb +11 -0
  102. data/lib/fedipub/maintenance/actors_updater.rb +66 -0
  103. data/lib/fedipub/maintenance/hosts_updater.rb +19 -0
  104. data/lib/fedipub/utils/actor.rb +85 -0
  105. data/lib/fedipub/utils/context.rb +12 -0
  106. data/lib/fedipub/utils/host.rb +54 -0
  107. data/lib/fedipub/utils/json_request.rb +41 -0
  108. data/lib/fedipub/utils/object.rb +131 -0
  109. data/lib/fedipub/utils/response_codes.rb +11 -0
  110. data/lib/fedipub/version.rb +1 -3
  111. data/lib/fedipub.rb +127 -4
  112. data/lib/fediverse/collection.rb +31 -0
  113. data/lib/fediverse/inbox.rb +110 -0
  114. data/lib/fediverse/node_info.rb +38 -0
  115. data/lib/fediverse/notifier.rb +90 -0
  116. data/lib/fediverse/request.rb +29 -0
  117. data/lib/fediverse/signature.rb +49 -0
  118. data/lib/fediverse/webfinger.rb +140 -0
  119. data/lib/fediverse.rb +5 -0
  120. data/lib/generators/fedipub/copy_client_policies/USAGE +8 -0
  121. data/lib/generators/fedipub/copy_client_policies/copy_client_policies_generator.rb +9 -0
  122. data/lib/generators/fedipub/copy_client_views/USAGE +8 -0
  123. data/lib/generators/fedipub/copy_client_views/copy_client_views_generator.rb +9 -0
  124. data/lib/generators/fedipub/copy_factories/USAGE +8 -0
  125. data/lib/generators/fedipub/copy_factories/copy_factories_generator.rb +43 -0
  126. data/lib/generators/fedipub/install/USAGE +9 -0
  127. data/lib/generators/fedipub/install/install_generator.rb +10 -0
  128. data/lib/generators/fedipub/install/templates/fedipub.rb +1 -0
  129. data/lib/generators/fedipub/install/templates/fedipub.yml +27 -0
  130. data/lib/tasks/factory_bot.rake +15 -0
  131. data/lib/tasks/fedipub_tasks.rake +13 -0
  132. metadata +259 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26b8e43a825419cc29580d3ac1aa08ed0963d9a6a2037e4df12367ecd8519d1f
4
- data.tar.gz: 2a8d9d19556046c52b6a9db3aba4682886449af998b3b15ab25ffeecbd712f96
3
+ metadata.gz: e48fe0306a23dd822ac8e755412635303612686408512d9cd36139050fc961a7
4
+ data.tar.gz: ca741f66e2d65b4501b9ae80465fde3500f81d65f55b56b0eef1d0a96bf2b97c
5
5
  SHA512:
6
- metadata.gz: 41a29d437859cc4d3e19aab1065e11ba3be55cfd27164b256d813fa83ea2931e7ca381a88816b870b0dde73d74f4fb00a86cc89a465879d7521e2fc229bc7708
7
- data.tar.gz: 5f086dff6232c1d0a156e06a5757aebe7ec172079bb4e01d0989026d0cb3d1e3c0c19266528aa3fbb5a557dc1c10effa269088f2d1625d1ad1b2e38b6effda42
6
+ metadata.gz: e96c6dcb707777a477078d15b750e2257dafecdd7c489a21f2c8beea9c965be237da1ed414ac27327e684874d840d11274336106defb1d1fe43df242b6b274e8
7
+ data.tar.gz: da1eca4e3c5d882a0dabec9668d521f1f9a707a6dfcbfe36d4ed6d42ffa761d0c7049ffc67d3d1666cae535d85af6ff80cdb11582ff4e6d068365e0949ed737c
data/README.md CHANGED
@@ -1,3 +1,71 @@
1
- Placeholder repository to publish a v0.0.1 on rubygems until we migrated the real code.
1
+ # Fedipub
2
2
 
3
- For now, check https://gitlab.com/fedipub/fedipub.
3
+ Fedipub is an engine that brings ActivityPub to Ruby on Rails application.
4
+
5
+ > [!note]
6
+ > This project was know as _Federails_ (and the `federails` gem and extensions) and has been
7
+ > renamed recently to Fedipub.
8
+
9
+ ## Community
10
+
11
+ You can join the [matrix chat room](https://matrix.to/#/#fedipub:matrix.org) to chat with humans.
12
+
13
+ Open issues or feature requests on the [issue tracker](https://gitlab.com/fedipub/fedipub/-/issues)
14
+
15
+ ## Features
16
+
17
+ This engine is meant to be used in Rails applications to add the ability to act as an ActivityPub server.
18
+
19
+ 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.
20
+
21
+ The general direction is to be able to:
22
+
23
+ - publish and subscribe to any type of content
24
+ - have a discovery endpoint (`webfinger`)
25
+ - have a following/followers system
26
+ - implement all the parts of the (RFC) labelled with **MUST** and **MUST NOT**
27
+ - implement some or all the parts of the RFC labelled with **SHOULD** and **SHOULD NOT**
28
+ - maybe implement the parts of the RFC labelled with **MAY**
29
+
30
+ ## Supported Ruby on Rails versions
31
+
32
+ This gem is tested against non end-of-life versions of [Ruby](https://endoflife.date/ruby) and [Rails](https://endoflife.date/rails):
33
+
34
+ - Ruby versions 3.3, 3.4 and 4.0
35
+ - Rails 7.2 to 8.1.
36
+
37
+ Feel free to open an issue if we missed something
38
+
39
+ It _may_ work on other versions, but we won't provide support.
40
+
41
+ ## Documentation
42
+
43
+ - [Usage](docs/usage.md)
44
+ - [Common questions](docs/faq.md)
45
+ - [Contributing](CONTRIBUTING.md)
46
+
47
+ ## Extensions
48
+
49
+ Extensions add extra features to Fedipub:
50
+
51
+ - [Fedipub Moderation](https://github.com/fedipub/fedipub-moderation/)
52
+ > A gem that provides trust & safety for Fedipub
53
+
54
+ ## License
55
+
56
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
57
+
58
+ ## Contributing
59
+
60
+ See [CONTRIBUTING](CONTRIBUTING.md) to have an overview of the process and the tools we use.
61
+
62
+ ### Contributors
63
+
64
+ - [echarp](https://gitlab.com/echarp)
65
+ - [James Smith](https://gitlab.com/floppy.uk)
66
+ - [Manuel Tancoigne](https://gitlab.com/mtancoigne)
67
+ - [pessi-v](https://github.com/pessi-v)
68
+
69
+ ### Indirect contributions
70
+
71
+ - Gitlab runners are graciously provided by [Coopaname](https://coopaname.coop), a French cooperative.
data/Rakefile CHANGED
@@ -1,12 +1,8 @@
1
- # frozen_string_literal: true
1
+ require 'bundler/setup'
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
3
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
4
+ load 'rails/tasks/engine.rake'
5
5
 
6
- RSpec::Core::RakeTask.new(:spec)
6
+ load 'rails/tasks/statistics.rake'
7
7
 
8
- require "rubocop/rake_task"
9
-
10
- RuboCop::RakeTask.new
11
-
12
- task default: %i[spec rubocop]
8
+ require 'bundler/gem_tasks'
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/fedipub .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,19 @@
1
+ module Fedipub
2
+ module Server
3
+ module RenderCollections
4
+ extend ActiveSupport::Concern
5
+
6
+ def render_collection(actor:, collection:, url_helper:, &items_block)
7
+ if params[:page].present?
8
+ render_collection_page(actor: actor, collection: collection, url_helper: url_helper, items_block: items_block)
9
+ else
10
+ render 'fedipub/server/shared/ordered_collection', locals: { collection: collection, url_helper: url_helper, actor: actor }
11
+ end
12
+ end
13
+
14
+ def render_collection_page(collection:, actor:, url_helper:, items_block:)
15
+ render 'fedipub/server/shared/ordered_collection_page', locals: { collection: collection, url_helper: url_helper, actor: actor, items_block: items_block }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ module Fedipub
2
+ module Client
3
+ class ActivitiesController < Fedipub::ClientController
4
+ before_action :authenticate_user!, only: [:feed]
5
+ before_action :authorize_action!
6
+
7
+ # GET /app/activities
8
+ # GET /app/activities.json
9
+ def index
10
+ @activities = policy_scope(Fedipub::Activity, policy_scope_class: Fedipub::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.fedipub_actor)
18
+ end
19
+
20
+ private
21
+
22
+ def authorize_action!
23
+ authorize(Fedipub::Activity, policy_class: Fedipub::Client::ActivityPolicy)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,54 @@
1
+ module Fedipub
2
+ module Client
3
+ class ActorsController < Fedipub::ClientController
4
+ before_action :set_actor, only: [:show]
5
+
6
+ # GET /app/actors
7
+ # GET /app/actors.json
8
+ def index
9
+ authorize Fedipub::Actor, policy_class: Fedipub::Client::ActorPolicy
10
+
11
+ @actors = policy_scope(Fedipub::Actor, policy_scope_class: Fedipub::Client::ActorPolicy::Scope).all
12
+ @actors = @actors.local if params[:local_only]
13
+ end
14
+
15
+ # GET /app/actors/1
16
+ # GET /app/actors/1.json
17
+ def show
18
+ render_show
19
+ end
20
+
21
+ # GET /app/actors/lookup
22
+ # GET /app/actors/lookup.json
23
+ def lookup
24
+ @actor = Fedipub::Actor.find_by_account account_param
25
+ authorize @actor, policy_class: Fedipub::Client::ActorPolicy
26
+ render_show
27
+ end
28
+
29
+ private
30
+
31
+ # Use callbacks to share common setup or constraints between actions.
32
+ def set_actor
33
+ @actor = Fedipub::Actor.find_param(params[:id])
34
+ authorize @actor, policy_class: Fedipub::Client::ActorPolicy
35
+ end
36
+
37
+ def account_param
38
+ params.require('account').strip
39
+ end
40
+
41
+ def render_show
42
+ respond_to do |format|
43
+ if @actor.tombstoned?
44
+ format.html { render :gone, status: :gone }
45
+ format.json { render json: { error: I18n.t('controller.actors.gone') }, status: :gone }
46
+ else
47
+ format.html { render :show }
48
+ format.json { render :show }
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,103 @@
1
+ module Fedipub
2
+ module Client
3
+ class FollowingsController < Fedipub::ClientController
4
+ before_action :authenticate_user!
5
+ before_action :skip_authorization, only: [:new, :create]
6
+ before_action :set_following, only: [:accept, :destroy]
7
+
8
+ # GET /app/followings/new?uri={uri}
9
+ def new
10
+ # Find actor (and fetch if necessary)
11
+ actor = Actor.find_or_create_by_federation_url(params.require(:uri))
12
+ # Redirect to local profile page which will have a follow button on it
13
+ redirect_to fedipub.client_actor_url(actor)
14
+ end
15
+
16
+ # PUT /app/followings/:id/accept
17
+ # PUT /app/followings/:id/accept.json
18
+ def accept
19
+ respond_to do |format|
20
+ url = fedipub.client_actor_url @following.actor
21
+ if @following.accept!
22
+ format.html { redirect_to url, notice: I18n.t('controller.followings.accept.success') }
23
+ format.json { render :show, status: :ok, location: @following }
24
+ else
25
+ format.html { redirect_to url, alert: I18n.t('controller.followings.accept.error') }
26
+ format.json { render json: @following.errors, status: Fedipub::Utils::ResponseCodes::UNPROCESSABLE_CONTENT }
27
+ end
28
+ end
29
+ end
30
+
31
+ # POST /app/followings
32
+ # POST /app/followings.json
33
+ def create
34
+ @following = Following.new(following_params)
35
+ @following.actor = current_user.fedipub_actor
36
+ authorize @following, policy_class: Fedipub::Client::FollowingPolicy
37
+
38
+ save_and_render
39
+ end
40
+
41
+ # POST /app/followings/follow
42
+ # POST /app/followings/follow.json
43
+ def follow
44
+ authorize Fedipub::Following, policy_class: Fedipub::Client::FollowingPolicy
45
+
46
+ begin
47
+ @following = Following.new_from_account following_account_params, actor: current_user.fedipub_actor
48
+ rescue ::ActiveRecord::RecordNotFound
49
+ # Renders a 422 instead of a 404
50
+ respond_to do |format|
51
+ format.html { redirect_to fedipub.client_actors_url, alert: I18n.t('controller.followings.follow.error') }
52
+ format.json { render json: { target_actor: ['does not exist'] }, status: Fedipub::Utils::ResponseCodes::UNPROCESSABLE_CONTENT }
53
+ end
54
+
55
+ return
56
+ end
57
+
58
+ save_and_render
59
+ end
60
+
61
+ # DELETE /app/followings/1
62
+ # DELETE /app/followings/1.json
63
+ def destroy
64
+ @following.destroy
65
+ respond_to do |format|
66
+ format.html { redirect_to fedipub.client_actor_url(@following.actor), notice: I18n.t('controller.followings.destroy.success') }
67
+ format.json { head :no_content }
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ # Use callbacks to share common setup or constraints between actions.
74
+ def set_following
75
+ @following = Following.find_param(params[:id])
76
+ authorize @following, policy_class: Fedipub::Client::FollowingPolicy
77
+ end
78
+
79
+ # Only allow a list of trusted parameters through.
80
+ def following_params
81
+ params.require(:following).permit(:target_actor_id)
82
+ end
83
+
84
+ def following_account_params
85
+ params.require(:account)
86
+ end
87
+
88
+ def save_and_render # rubocop:disable Metrics/AbcSize
89
+ url = fedipub.client_actor_url current_user.fedipub_actor
90
+
91
+ respond_to do |format|
92
+ if @following.save
93
+ format.html { redirect_to url, notice: I18n.t('controller.followings.save_and_render.success') }
94
+ format.json { render :show, status: :created, location: @following }
95
+ else
96
+ format.html { redirect_to url, alert: I18n.t('controller.followings.save_and_render.error') }
97
+ format.json { render json: @following.errors, status: Fedipub::Utils::ResponseCodes::UNPROCESSABLE_CONTENT }
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,9 @@
1
+ module Fedipub
2
+ class ClientController < Fedipub.configuration.base_client_controller.constantize
3
+ include Pundit::Authorization
4
+
5
+ after_action :verify_authorized
6
+
7
+ layout Fedipub.configuration.app_layout if Fedipub.configuration.app_layout
8
+ end
9
+ end
@@ -0,0 +1,78 @@
1
+ require 'fediverse/inbox'
2
+
3
+ module Fedipub
4
+ module Server
5
+ class ActivitiesController < Fedipub::ServerController
6
+ include Fedipub::Server::RenderCollections
7
+
8
+ before_action :set_activity, only: [:show]
9
+
10
+ # GET /federation/activities
11
+ # GET /federation/actors/1/outbox.json
12
+ def outbox
13
+ authorize Fedipub::Activity, policy_class: Fedipub::Server::ActivityPolicy
14
+
15
+ actor = Actor.find_param(params[:actor_id])
16
+ activities = policy_scope(Fedipub::Activity, policy_scope_class: Fedipub::Server::ActivityPolicy::Scope).where(actor: actor).order(created_at: :desc)
17
+
18
+ render_collection(
19
+ collection: activities.page(params[:page]),
20
+ actor: actor,
21
+ url_helper: :server_actor_outbox_url
22
+ ) do |builder, items|
23
+ builder.array! items, partial: 'fedipub/server/activities/activity', as: :activity, context: false
24
+ end
25
+ end
26
+
27
+ # GET /federation/actors/1/activities/1.json
28
+ def show; end
29
+
30
+ # POST /federation/actors/1/inbox
31
+ def create
32
+ skip_authorization
33
+
34
+ payload = payload_from_params
35
+ return head Fedipub::Utils::ResponseCodes::UNPROCESSABLE_CONTENT unless payload
36
+
37
+ if Fediverse::Inbox.dispatch_request(payload)
38
+ head :created
39
+ else
40
+ head Fedipub::Utils::ResponseCodes::UNPROCESSABLE_CONTENT
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # Use callbacks to share common setup or constraints between actions.
47
+ def set_activity
48
+ @activity = Actor.find_param(params[:actor_id]).activities.find_param(params[:id])
49
+ authorize @activity, policy_class: Fedipub::Server::ActivityPolicy
50
+ end
51
+
52
+ # Only allow a list of trusted parameters through.
53
+ def activity_params
54
+ params.fetch(:activity, {})
55
+ end
56
+
57
+ def payload_from_params
58
+ payload_string = request.body.read
59
+ request.body.rewind if request.body.respond_to? :rewind
60
+
61
+ begin
62
+ payload = JSON.parse(payload_string)
63
+ rescue JSON::ParserError
64
+ return
65
+ end
66
+
67
+ hash = JSON::LD::API.compact payload, payload['@context']
68
+ validate_payload hash
69
+ end
70
+
71
+ def validate_payload(hash)
72
+ return unless hash['@context'] && hash['id'] && hash['type'] && hash['actor'] && hash['object']
73
+
74
+ hash
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,50 @@
1
+ module Fedipub
2
+ module Server
3
+ class ActorsController < Fedipub::ServerController
4
+ include Fedipub::Server::RenderCollections
5
+
6
+ before_action :set_actor, only: [:show, :followers, :following]
7
+
8
+ # GET /federation/actors/1
9
+ # GET /federation/actors/1.json
10
+ def show
11
+ status = @actor.tombstoned? ? :gone : :ok
12
+ render :show, status: status
13
+ end
14
+
15
+ # GET /federation/actors/:id/followers
16
+ # GET /federation/actors/:id/followers.json
17
+ def followers
18
+ @actors = @actor.followers.order(created_at: :desc)
19
+ render_collection(
20
+ collection: @actors.page(params[:page]),
21
+ actor: @actor,
22
+ url_helper: :followers_server_actor_url
23
+ ) do |builder, items|
24
+ builder.array! items.map(&:federated_url)
25
+ end
26
+ end
27
+
28
+ # GET /federation/actors/:id/followers
29
+ # GET /federation/actors/:id/followers.json
30
+ def following
31
+ @actors = @actor.follows.order(created_at: :desc)
32
+ render_collection(
33
+ collection: @actors.page(params[:page]),
34
+ actor: @actor,
35
+ url_helper: :following_server_actor_url
36
+ ) do |builder, items|
37
+ builder.array! items.map(&:federated_url)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # Use callbacks to share common setup or constraints between actions.
44
+ def set_actor
45
+ @actor = Actor.find_param(params[:id])
46
+ authorize @actor, policy_class: Fedipub::Server::ActorPolicy
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,19 @@
1
+ module Fedipub
2
+ module Server
3
+ class FollowingsController < Fedipub::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: Fedipub::Server::FollowingPolicy
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ module Fedipub
2
+ module Server
3
+ class NodeinfoController < Fedipub::ServerController
4
+ def index
5
+ skip_authorization
6
+
7
+ render formats: [:nodeinfo]
8
+ end
9
+
10
+ def show # rubocop:todo Metrics/AbcSize
11
+ skip_authorization
12
+
13
+ @total = @active_halfyear = @active_month = 0
14
+ @has_user_counts = false
15
+ Fedipub::Configuration.actor_types.each_value do |config|
16
+ next unless (method = config[:user_count_method]&.to_sym)
17
+
18
+ @has_user_counts = true
19
+ model = config[:class]
20
+ @total += model.send(method, nil)
21
+ @active_month += model.send(method, (30.days.ago)...Time.current)
22
+ @active_halfyear += model.send(method, (180.days.ago)...Time.current)
23
+ end
24
+ render formats: [:nodeinfo]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ module Fedipub
2
+ module Server
3
+ # Controller to render ActivityPub representation of entities configured with Fedipub::DataEntity
4
+ class PublishedController < Fedipub::ServerController
5
+ def show
6
+ @publishable = type_scope.find_untombstoned_by!(url_param => params[:id])
7
+ authorize @publishable, policy_class: Fedipub::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 = Fedipub.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
@@ -0,0 +1,45 @@
1
+ require 'fediverse/webfinger'
2
+
3
+ module Fedipub
4
+ module Server
5
+ class WebFingerController < Fedipub::ServerController
6
+ def find
7
+ skip_authorization
8
+
9
+ resource = params.require(:resource)
10
+ case resource
11
+ when %r{^https?://.+}
12
+ @user = Fedipub::Actor.find_by_federation_url!(resource).entity # rubocop:disable Rails/DynamicFindBy
13
+ when /^acct:.+/
14
+ actor = Fedipub::Actor.find_local_by_username(username)
15
+ raise Fedipub::Actor::TombstonedError if actor&.tombstoned?
16
+
17
+ @user = actor&.entity
18
+ end
19
+ raise ActiveRecord::RecordNotFound if @user.nil?
20
+
21
+ render formats: [:jrd]
22
+ end
23
+
24
+ def host_meta
25
+ skip_authorization
26
+
27
+ render formats: [:xrd]
28
+ end
29
+
30
+ # TODO: complete missing endpoints
31
+
32
+ private
33
+
34
+ def username
35
+ return @username if instance_variable_defined? :@username
36
+
37
+ account = Fediverse::Webfinger.split_account params.require(:resource)
38
+ # Fail early if user don't _seems_ local
39
+ raise ActiveRecord::RecordNotFound unless account && Fediverse::Webfinger.local_user?(account)
40
+
41
+ @username = account[:username]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,37 @@
1
+ module Fedipub
2
+ class ServerController < ::ActionController::Base # rubocop:disable Rails/ApplicationController
3
+ include Pundit::Authorization
4
+
5
+ after_action :verify_authorized
6
+
7
+ protect_from_forgery with: :null_session
8
+ helper Fedipub::ServerHelper
9
+
10
+ rescue_from ActiveRecord::RecordNotFound, with: :error_not_found
11
+ rescue_from Fedipub::Actor::TombstonedError,
12
+ Fedipub::DataEntity::TombstonedError,
13
+ with: :error_gone
14
+
15
+ private
16
+
17
+ def error_fallback(exception, fallback_message, status)
18
+ message = exception&.message || fallback_message
19
+ respond_to do |format|
20
+ format.jrd { head status }
21
+ format.xrd { head status }
22
+ format.activitypub { head status }
23
+ format.nodeinfo { head status }
24
+ format.json { render json: { error: message }, status: status }
25
+ format.html { raise exception }
26
+ end
27
+ end
28
+
29
+ def error_not_found(exception = nil)
30
+ error_fallback(exception, 'Resource not found', :not_found)
31
+ end
32
+
33
+ def error_gone(exception = nil)
34
+ error_fallback(exception, 'Resource is gone', :gone)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ require 'fedipub/utils/context'
2
+
3
+ module Fedipub
4
+ module ServerHelper
5
+ def remote_follow_url
6
+ method_name = Fedipub.configuration.remote_follow_url_method.to_s
7
+ if method_name.starts_with? 'fedipub.'
8
+ send(method_name.gsub('fedipub.', ''))
9
+ else
10
+ Rails.application.routes.url_helpers.send(method_name)
11
+ end
12
+ end
13
+
14
+ def set_json_ld_context(json, additional: nil)
15
+ json.set! '@context', Fedipub::Utils::Context.generate(additional: additional)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ module Fedipub
2
+ class ApplicationJob < ActiveJob::Base
3
+ queue_as { Configuration.job_queue.to_sym }
4
+
5
+ discard_on ActiveJob::DeserializationError
6
+ after_discard do |_job, exception|
7
+ Rails.logger.info exception.to_s
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'fediverse/notifier'
2
+
3
+ module Fedipub
4
+ class FetchNodeinfoJob < ApplicationJob
5
+ # @param domain [String] Domain to create/update
6
+ def perform(domain)
7
+ Fedipub::Host.create_or_update domain, min_update_interval: Fedipub::Configuration.remote_entities_cache_duration
8
+ end
9
+ end
10
+ end