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
@@ -0,0 +1 @@
1
+ json.partial! 'federails/client/followings/following', following: @following
@@ -0,0 +1,9 @@
1
+ context = true unless context == false
2
+ json.set! '@context', 'https://www.w3.org/ns/activitystreams' if context
3
+
4
+ json.id Federails::Engine.routes.url_helpers.server_actor_activity_url activity.actor, activity
5
+ json.type activity.action
6
+ json.actor activity.actor.federated_url
7
+ json.to ['https://www.w3.org/ns/activitystreams#Public']
8
+ json.cc [activity.actor.followers_url]
9
+ json.object activity.entity.federated_url
@@ -0,0 +1,18 @@
1
+ json.set!('@context', 'https://www.w3.org/ns/activitystreams')
2
+ collection_id = @actor.outbox_url
3
+ json.id collection_id
4
+ json.type 'OrderedCollectionPage'
5
+ json.totalItems @total_activities
6
+ json.first collection_id
7
+ json.last @activities.total_pages == 1 ? Federails::Engine.routes.url_helpers.server_actor_outbox_url(@actor) : Federails::Engine.routes.url_helpers.server_actor_outbox_url(@actor, page: @activities.total_pages)
8
+ json.current do |j|
9
+ j.type 'OrderedCollectionPage'
10
+ j.id @activities.current_page == 1 ? Federails::Engine.routes.url_helpers.server_actor_outbox_url(@actor) : Federails::Engine.routes.url_helpers.server_actor_outbox_url(@actor, page: @activities.current_page)
11
+ j.partOf collection_id
12
+ j.next @activities.next_page
13
+ j.prev @activities.prev_page
14
+ j.totalItems @total_activities
15
+ j.orderedItems do
16
+ json.array! @activities, partial: 'federails/server/activities/activity', as: :activity, context: false
17
+ end
18
+ end
@@ -0,0 +1 @@
1
+ json.partial! 'federails/server/activities/activity', activity: @activity
@@ -0,0 +1,11 @@
1
+ json.set! '@context', 'https://www.w3.org/ns/activitystreams'
2
+
3
+ json.id actor.federated_url
4
+ json.name actor.name
5
+ json.type actor.entity_configuration[:actor_type]
6
+ json.preferredUsername actor.username
7
+ json.inbox actor.inbox_url
8
+ json.outbox actor.outbox_url
9
+ json.followers actor.followers_url
10
+ json.following actor.followings_url
11
+ json.url actor.profile_url
@@ -0,0 +1,18 @@
1
+ json.set!('@context', 'https://www.w3.org/ns/activitystreams')
2
+ collection_id = @actor.followers_url
3
+ json.id collection_id
4
+ json.type 'OrderedCollectionPage'
5
+ json.totalItems @total_actors
6
+ json.first Federails::Engine.routes.url_helpers.followers_server_actor_url(@actor)
7
+ json.last @actors.total_pages == 1 ? Federails::Engine.routes.url_helpers.followers_server_actor_url(@actor) : Federails::Engine.routes.url_helpers.followers_server_actor_url(@actor, page: @actors.total_pages)
8
+ json.current do |j|
9
+ j.type 'OrderedCollectionPage'
10
+ j.id @actors.current_page == 1 ? Federails::Engine.routes.url_helpers.followers_server_actor_url(@actor) : Federails::Engine.routes.url_helpers.followers_server_actor_url(@actor, page: @actors.current_page)
11
+ j.partOf collection_id
12
+ j.next @actors.next_page
13
+ j.prev @actors.prev_page
14
+ j.totalItems @total_actors
15
+ j.orderedItems do
16
+ json.array! @actors.map(&:federated_url)
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ json.set!('@context', 'https://www.w3.org/ns/activitystreams')
2
+ collection_id = @actor.followings_url
3
+ json.id collection_id
4
+ json.type 'OrderedCollectionPage'
5
+ json.totalItems @total_actors
6
+ json.first Federails::Engine.routes.url_helpers.following_server_actor_url(@actor)
7
+ json.last @actors.total_pages == 1 ? Federails::Engine.routes.url_helpers.following_server_actor_url(@actor) : Federails::Engine.routes.url_helpers.following_server_actor_url(@actor, page: @actors.total_pages)
8
+ json.current do |j|
9
+ j.type 'OrderedCollectionPage'
10
+ j.id @actors.current_page == 1 ? Federails::Engine.routes.url_helpers.following_server_actor_url(@actor) : Federails::Engine.routes.url_helpers.following_server_actor_url(@actor, page: @actors.current_page)
11
+ j.partOf collection_id
12
+ j.next @actors.next_page
13
+ j.prev @actors.prev_page
14
+ j.totalItems @total_actors
15
+ j.orderedItems do
16
+ json.array! @actors.map(&:federated_url)
17
+ end
18
+ end
@@ -0,0 +1 @@
1
+ json.partial! 'federails/server/actors/actor', actor: @actor
@@ -0,0 +1,7 @@
1
+ context = true unless context == false
2
+ json.set! '@context', 'https://www.w3.org/ns/activitystreams' if context
3
+
4
+ json.id following.federated_url
5
+ json.type 'Follow'
6
+ json.actor following.actor.federated_url
7
+ json.object following.target_actor.federated_url
@@ -0,0 +1 @@
1
+ json.partial! 'federails/server/followings/following', following: @following
@@ -0,0 +1,6 @@
1
+ json.links [
2
+ {
3
+ rel: 'https://nodeinfo.diaspora.software/ns/schema/2.0',
4
+ href: show_node_info_url,
5
+ },
6
+ ]
@@ -0,0 +1,19 @@
1
+ json.version '2.0'
2
+ # FIXME: Use configuration values when created
3
+ json.software name: Federails::Configuration.app_name,
4
+ version: Federails::Configuration.app_version
5
+ json.protocols [
6
+ 'activitypub',
7
+ ]
8
+ # FIXME: When server is in good shape: update outbounds
9
+ # http://nodeinfo.diaspora.software/ns/schema/2.0 for possible values
10
+ json.services inbound: [],
11
+ outbound: []
12
+ # FIXME: Don't hardcode this
13
+ json.openRegistrations true
14
+ json.usage users: {
15
+ total: @total,
16
+ activeMonth: @active_month,
17
+ activeHalfyear: @active_halfyear,
18
+ }
19
+ json.metadata({})
@@ -0,0 +1,20 @@
1
+ json.subject params[:resource]
2
+
3
+ links = [
4
+ # Federation actor URL
5
+ {
6
+ rel: 'self',
7
+ type: 'application/activity+json',
8
+ href: @user.actor.federated_url,
9
+ },
10
+ ]
11
+
12
+ # User profile URL if configured
13
+ # TODO: Add a profile controller/action in dummy to test this
14
+ if @user.actor.profile_url
15
+ links.push rel: 'https://webfinger.net/rel/profile-page',
16
+ type: 'text/html',
17
+ href: @user.actor.profile_url
18
+ end
19
+
20
+ json.links links
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <XRD
3
+ xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
4
+ <Link rel="lrdd" type="application/xrd+xml" template="<%= webfinger_url %>?resource={uri}" />
5
+ </XRD>
data/config/routes.rb CHANGED
@@ -1,2 +1,45 @@
1
1
  Federails::Engine.routes.draw do
2
+ if Federails.configuration.enable_discovery
3
+ scope path: '/' do
4
+ get '/.well-known/webfinger', to: 'server/web_finger#find', as: :webfinger
5
+ get '/.well-known/host-meta', to: 'server/web_finger#host_meta', as: :host_meta
6
+ get '/.well-known/nodeinfo', to: 'server/nodeinfo#index', as: :node_info
7
+ get '/nodeinfo/2.0', to: 'server/nodeinfo#show', as: :show_node_info
8
+ end
9
+ end
10
+
11
+ if Federails.configuration.client_routes_path
12
+ scope Federails.configuration.client_routes_path, module: :client, as: :client do
13
+ resources :activities, only: [:index, :feed]
14
+ resources :actors, only: [:index, :show] do
15
+ collection do
16
+ get :lookup, to: 'actors#lookup'
17
+ end
18
+ resources :activities, only: [:index]
19
+ end
20
+ get :feed, to: 'activities#feed'
21
+ resources :followings, only: [:create, :destroy] do
22
+ collection do
23
+ post :follow, to: 'followings#follow'
24
+ end
25
+
26
+ member do
27
+ put :accept, to: 'followings#accept'
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ scope Federails.configuration.server_routes_path, module: :server, as: :server do
34
+ resources :actors, only: [:show] do
35
+ member do
36
+ get :followers
37
+ get :following
38
+ end
39
+ get :outbox, to: 'activities#outbox'
40
+ post :inbox, to: 'activities#create'
41
+ resources :activities, only: [:show]
42
+ resources :followings, only: [:show]
43
+ end
44
+ end
2
45
  end
@@ -0,0 +1,24 @@
1
+ class CreateFederailsActors < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :federails_actors do |t|
4
+ t.string :name
5
+ t.string :federated_url
6
+ t.string :username
7
+ t.string :server
8
+ t.string :inbox_url
9
+ t.string :outbox_url
10
+ t.string :followers_url
11
+ t.string :followings_url
12
+ t.string :profile_url
13
+
14
+ t.references :user, null: true, foreign_key: { to_table: Federails.configuration.user_table }
15
+
16
+ t.timestamps
17
+ t.index :federated_url, unique: true
18
+ end
19
+ remove_foreign_key :federails_actors, :users if foreign_key_exists?(:federails_actors, :users)
20
+ remove_index :federails_actors, :user_id
21
+ add_index :federails_actors, :user_id, unique: true
22
+ add_foreign_key :federails_actors, :users
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ class CreateFederailsFollowings < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :federails_followings do |t|
4
+ t.references :actor, null: false, foreign_key: { to_table: :federails_actors }
5
+ t.references :target_actor, null: false, foreign_key: { to_table: :federails_actors }
6
+ t.integer :status, default: 0
7
+ t.string :federated_url
8
+
9
+ t.timestamps
10
+
11
+ t.index [:actor_id, :target_actor_id], unique: true
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ class CreateFederailsActivities < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :federails_activities do |t|
4
+ t.references :entity, polymorphic: true, null: false
5
+ t.string :action, null: false, default: nil
6
+ t.references :actor, null: false, foreign_key: { to_table: :federails_actors }
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class ChangeActorEntityRelToPolymorphic < ActiveRecord::Migration[7.0]
2
+ def change
3
+ remove_foreign_key :federails_actors, column: :user_id, to_table: Federails::Configuration.user_table
4
+ remove_index :federails_actors, :user_id, unique: true
5
+ change_table :federails_actors do |t|
6
+ t.rename :user_id, :entity_id
7
+ t.string :entity_type, null: true, default: Federails::Configuration.user_class&.demodulize
8
+ t.index [:entity_type, :entity_id], name: 'index_federails_actors_on_entity', unique: true
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,88 @@
1
+ module Federails
2
+ # rubocop:disable Style/ClassVars
3
+ module Configuration
4
+ # Application name, used in well-known and nodeinfo endpoints
5
+ mattr_accessor :app_name
6
+ @@app_name = nil
7
+
8
+ # Application version, used in well-known and nodeinfo endpoints
9
+ mattr_accessor :app_version
10
+ @@app_version = nil
11
+
12
+ # Force https urls in various rendered content (currently in webfinger views)
13
+ mattr_accessor :force_ssl
14
+ @@force_ssl = nil
15
+
16
+ # Site hostname
17
+ mattr_reader :site_host
18
+ @@site_host = nil
19
+
20
+ # Site port
21
+ mattr_reader :site_port
22
+ @@site_port = nil
23
+
24
+ # Whether to enable ".well-known" and "nodeinfo" endpoints
25
+ mattr_accessor :enable_discovery
26
+ @@enable_discovery = true
27
+
28
+ # Site port
29
+ mattr_accessor :app_layout
30
+ @@app_layout = nil
31
+
32
+ # User class name
33
+ # @deprecated Kept for upgrade compatibility only
34
+ mattr_accessor :user_class
35
+ @@user_class = '::User'
36
+
37
+ # Route path for the federation URLs (to "Federails::Server::*" controllers)
38
+ mattr_accessor :server_routes_path
39
+ @@server_routes_path = :federation
40
+
41
+ # Route path for the webapp URLs (to "Federails::Client::*" controllers)
42
+ mattr_accessor :client_routes_path
43
+ @@client_routes_path = :app
44
+
45
+ # Method to use for links to user profiles
46
+ # @deprecated Set profile_url_method option on acts_as_federails_actor instead
47
+ mattr_accessor :user_profile_url_method
48
+ @@user_profile_url_method = nil
49
+
50
+ # Attribute in the user model to use as the user's name
51
+ # @deprecated Set name_field option on acts_as_federails_actor instead
52
+ #
53
+ # It only have sense if you have a separate username attribute
54
+ mattr_accessor :user_name_field
55
+ @@user_name_field = nil
56
+
57
+ # Attribute in the user model to use as the username for local actors
58
+ # @deprecated Set username_field option on acts_as_federails_actor instead
59
+ mattr_accessor :user_username_field
60
+ @@user_username_field = :id
61
+
62
+ ##
63
+ # @return [String] Table used for user model
64
+ # @deprecated Kept for upgrade compatibility only
65
+ def self.user_table
66
+ @@user_class&.constantize&.table_name
67
+ end
68
+
69
+ def self.site_host=(value)
70
+ @@site_host = value
71
+ Federails::Engine.routes.default_url_options[:host] = value
72
+ end
73
+
74
+ def self.site_port=(value)
75
+ @@site_port = value
76
+ Federails::Engine.routes.default_url_options[:port] = value
77
+ end
78
+
79
+ # List of entity types
80
+ mattr_reader :entity_types
81
+ @@entity_types = {}
82
+
83
+ def self.register_entity(klass, config = {})
84
+ @@entity_types[klass.name] = config.merge(class: klass)
85
+ end
86
+ end
87
+ # rubocop:enable Style/ClassVars
88
+ end
@@ -1,5 +1,11 @@
1
1
  module Federails
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Federails
4
+
5
+ config.generators do |g|
6
+ g.test_framework :rspec
7
+ g.fixture_replacement :factory_bot
8
+ g.factory_bot dir: 'spec/factories'
9
+ end
4
10
  end
5
11
  end
@@ -0,0 +1,54 @@
1
+ module Federails
2
+ module Utils
3
+ class Host
4
+ class << self
5
+ COMMON_PORTS = [80, 443].freeze
6
+
7
+ ##
8
+ # @return [String] Host and port of the current instance
9
+ def localhost
10
+ uri = URI.parse Federails.configuration.site_host
11
+ host_and_port (uri.host || 'localhost'), Federails.configuration.site_port
12
+ end
13
+
14
+ ##
15
+ # Checks if the given URL points somewhere on current instance
16
+ #
17
+ # @param url [String] URL to check
18
+ #
19
+ # @return [true, false]
20
+ def local_url?(url)
21
+ uri = URI.parse url
22
+ host = host_and_port uri.host, uri.port
23
+ localhost == host
24
+ end
25
+
26
+ ##
27
+ # Gets the route on the current instance, or nil
28
+ #
29
+ # @param url [String] URL to check
30
+ #
31
+ # @return [ActionDispatch::Routing::RouteSet, nil] nil when URL do not match a route
32
+ def local_route(url)
33
+ return nil unless local_url? url
34
+
35
+ Rails.application.routes.recognize_path(url)
36
+ rescue ActionController::RoutingError
37
+ nil
38
+ end
39
+
40
+ private
41
+
42
+ def host_and_port(host, port)
43
+ port_string = if port.present? && COMMON_PORTS.exclude?(port)
44
+ ":#{port}"
45
+ else
46
+ ''
47
+ end
48
+
49
+ "#{host}#{port_string}"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,3 +1,3 @@
1
1
  module Federails
2
- VERSION = "0.0.1"
2
+ VERSION = '0.1.0'.freeze
3
3
  end
data/lib/federails.rb CHANGED
@@ -1,6 +1,36 @@
1
- require "federails/version"
2
- require "federails/engine"
1
+ require 'federails/version'
2
+ require 'federails/engine'
3
+ require 'federails/configuration'
3
4
 
5
+ # rubocop:disable Style/ClassVars
4
6
  module Federails
5
- # Your code goes here...
7
+ mattr_reader :configuration
8
+ @@configuration = Configuration
9
+
10
+ # Make factories available
11
+ config.factory_bot.definition_file_paths += [File.expand_path('spec/factories', __dir__)] if defined?(FactoryBotRails)
12
+
13
+ def self.configure
14
+ yield @@configuration
15
+ end
16
+
17
+ def self.config_from(name) # rubocop:disable Metrics/MethodLength
18
+ config = Rails.application.config_for name
19
+ [
20
+ :app_name,
21
+ :app_version,
22
+ :force_ssl,
23
+ :site_host,
24
+ :site_port,
25
+ :enable_discovery,
26
+ :app_layout,
27
+ :user_class, # @deprecated
28
+ :server_routes_path,
29
+ :client_routes_path,
30
+ :user_profile_url_method, # @deprecated
31
+ :user_name_field, # @deprecated
32
+ :user_username_field, # @deprecated
33
+ ].each { |key| Configuration.send :"#{key}=", config[key] }
34
+ end
6
35
  end
36
+ # rubocop:enable Style/ClassVars
@@ -0,0 +1,71 @@
1
+ require 'fediverse/request'
2
+
3
+ module Fediverse
4
+ class Inbox
5
+ class << self
6
+ def dispatch_request(payload)
7
+ case payload['type']
8
+ when 'Create'
9
+ handle_create_request payload
10
+ when 'Follow'
11
+ handle_create_follow_request payload
12
+ when 'Accept'
13
+ handle_accept_request payload
14
+ when 'Undo'
15
+ handle_undo_request payload
16
+ else
17
+ # FIXME: Fails silently
18
+ # raise NotImplementedError
19
+ Rails.logger.debug { "Unhandled activity type: #{payload['type']}" }
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def handle_create_request(payload)
26
+ activity = Request.get(payload['object'])
27
+ case activity['type']
28
+ when 'Follow'
29
+ handle_create_follow_request activity
30
+ when 'Note'
31
+ handle_create_note activity
32
+ end
33
+ end
34
+
35
+ def handle_create_follow_request(activity)
36
+ actor = Federails::Actor.find_or_create_by_object activity['actor']
37
+ target_actor = Federails::Actor.find_or_create_by_object activity['object']
38
+
39
+ Federails::Following.create! actor: actor, target_actor: target_actor, federated_url: activity['id']
40
+ end
41
+
42
+ def handle_create_note(activity)
43
+ actor = Federails::Actor.find_or_create_by_object activity['attributedTo']
44
+ Note.create! actor: actor, content: activity['content'], federated_url: activity['id']
45
+ end
46
+
47
+ def handle_accept_request(payload)
48
+ activity = Request.get(payload['object'])
49
+ raise "Can't accept things that are not Follow" unless activity['type'] == 'Follow'
50
+
51
+ actor = Federails::Actor.find_or_create_by_object activity['actor']
52
+ target_actor = Federails::Actor.find_or_create_by_object activity['object']
53
+ raise 'Follow not accepted by target actor but by someone else' if payload['actor'] != target_actor.federated_url
54
+
55
+ follow = Federails::Following.find_by actor: actor, target_actor: target_actor
56
+ follow.accept!
57
+ end
58
+
59
+ def handle_undo_request(payload)
60
+ activity = payload['object']
61
+ raise "Can't undo things that are not Follow" unless activity['type'] == 'Follow'
62
+
63
+ actor = Federails::Actor.find_or_create_by_object activity['actor']
64
+ target_actor = Federails::Actor.find_or_create_by_object activity['object']
65
+
66
+ follow = Federails::Following.find_by actor: actor, target_actor: target_actor
67
+ follow&.destroy
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,21 @@
1
+ module Fediverse
2
+ class Notifier
3
+ class << self
4
+ def post_to_inboxes(activity)
5
+ actors = activity.recipients
6
+
7
+ Rails.logger.debug('Nobody to notice') && return if actors.count.zero?
8
+
9
+ message = Federails::ApplicationController.renderer.new.render(
10
+ template: 'federails/server/activities/show',
11
+ assigns: { activity: activity },
12
+ format: :json
13
+ )
14
+ actors.each do |actor|
15
+ Rails.logger.debug { "Sending activity ##{activity.id} to #{actor.inbox_url}" }
16
+ Faraday.post actor.inbox_url, message, 'Content-Type' => 'application/json', 'Accept' => 'application/json'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,38 @@
1
+ require 'json/ld'
2
+
3
+ module Fediverse
4
+ class Request
5
+ BASE_HEADERS = {
6
+ 'Content-Type' => 'application/json',
7
+ 'Accept' => 'application/json',
8
+ }.freeze
9
+
10
+ def initialize(id)
11
+ @id = id
12
+ end
13
+
14
+ def get
15
+ Rails.logger.debug { "GET #{@id}" }
16
+ @response = Faraday.get(@id, nil, BASE_HEADERS)
17
+ response_to_json
18
+ end
19
+
20
+ class << self
21
+ def get(id)
22
+ new(id).get
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def response_to_json
29
+ begin
30
+ body = JSON.parse @response.body
31
+ rescue JSON::ParserError
32
+ return
33
+ end
34
+
35
+ JSON::LD::API.compact body, body['@context']
36
+ end
37
+ end
38
+ end