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,40 @@
1
+ module Federails
2
+ class Activity < ApplicationRecord
3
+ belongs_to :entity, polymorphic: true
4
+ belongs_to :actor
5
+
6
+ scope :feed_for, lambda { |actor|
7
+ actor_ids = []
8
+ Following.accepted.where(actor: actor).find_each do |following|
9
+ actor_ids << following.target_actor_id
10
+ end
11
+ where(actor_id: actor_ids)
12
+ }
13
+
14
+ after_create_commit :post_to_inboxes
15
+
16
+ def recipients # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
17
+ return [] unless actor.local?
18
+
19
+ actors = []
20
+ case action
21
+ when 'Create'
22
+ actors.push(entity.target_actor) if entity_type == 'Federails::Following'
23
+ # FIXME: Move this to dummy, somehow
24
+ actors.push(*actor.followers) if entity_type == 'Note'
25
+ when 'Accept'
26
+ actors.push(entity.actor) if entity_type == 'Federails::Following'
27
+ when 'Undo'
28
+ actors.push(entity.target_actor) if entity_type == 'Federails::Following'
29
+ end
30
+
31
+ actors
32
+ end
33
+
34
+ private
35
+
36
+ def post_to_inboxes
37
+ NotifyInboxJob.perform_later(self)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,152 @@
1
+ require 'federails/utils/host'
2
+ require 'fediverse/webfinger'
3
+
4
+ module Federails
5
+ class Actor < ApplicationRecord # rubocop:disable Metrics/ClassLength
6
+ validates :federated_url, presence: { unless: :entity }, uniqueness: { unless: :entity }
7
+ validates :username, presence: { unless: :entity }
8
+ validates :server, presence: { unless: :entity }
9
+ validates :inbox_url, presence: { unless: :entity }
10
+ validates :outbox_url, presence: { unless: :entity }
11
+ validates :followers_url, presence: { unless: :entity }
12
+ validates :followings_url, presence: { unless: :entity }
13
+ validates :profile_url, presence: { unless: :entity }
14
+ validates :entity_id, uniqueness: { scope: :entity_type }, if: :local?
15
+
16
+ belongs_to :entity, polymorphic: true, optional: true
17
+ # FIXME: Handle this with something like undelete
18
+ has_many :activities, dependent: :destroy
19
+ has_many :activities_as_entity, class_name: 'Federails::Activity', as: :entity, dependent: :destroy
20
+ has_many :following_followers, class_name: 'Federails::Following', foreign_key: :target_actor_id, dependent: :destroy, inverse_of: :target_actor
21
+ has_many :following_follows, class_name: 'Federails::Following', dependent: :destroy, inverse_of: :actor
22
+ has_many :followers, source: :actor, through: :following_followers
23
+ has_many :follows, source: :target_actor, through: :following_follows
24
+
25
+ scope :local, -> { where.not(entity: nil) }
26
+
27
+ def local?
28
+ entity.present?
29
+ end
30
+
31
+ def federated_url
32
+ local? ? Federails::Engine.routes.url_helpers.server_actor_url(self) : attributes['federated_url'].presence
33
+ end
34
+
35
+ def username
36
+ return attributes['username'] unless local?
37
+
38
+ entity.send(entity_configuration[:username_field]).to_s
39
+ end
40
+
41
+ def name
42
+ value = (entity.send(entity_configuration[:name_field]).to_s if local?)
43
+
44
+ value || attributes['name'] || username
45
+ end
46
+
47
+ def server
48
+ local? ? Utils::Host.localhost : attributes['server']
49
+ end
50
+
51
+ def inbox_url
52
+ local? ? Federails::Engine.routes.url_helpers.server_actor_inbox_url(self) : attributes['inbox_url']
53
+ end
54
+
55
+ def outbox_url
56
+ local? ? Federails::Engine.routes.url_helpers.server_actor_outbox_url(self) : attributes['outbox_url']
57
+ end
58
+
59
+ def followers_url
60
+ local? ? Federails::Engine.routes.url_helpers.followers_server_actor_url(self) : attributes['followers_url']
61
+ end
62
+
63
+ def followings_url
64
+ local? ? Federails::Engine.routes.url_helpers.following_server_actor_url(self) : attributes['followings_url']
65
+ end
66
+
67
+ def profile_url
68
+ return attributes['profile_url'].presence unless local?
69
+
70
+ method = entity_configuration[:profile_url_method]
71
+ return Federails::Engine.routes.url_helpers.server_actor_url self unless method
72
+
73
+ Rails.application.routes.url_helpers.send method, [entity]
74
+ end
75
+
76
+ def at_address
77
+ "#{username}@#{server}"
78
+ end
79
+
80
+ def short_at_address
81
+ local? ? "@#{username}" : at_address
82
+ end
83
+
84
+ def follows?(actor)
85
+ list = following_follows.where target_actor: actor
86
+ return list.first if list.count == 1
87
+
88
+ false
89
+ end
90
+
91
+ def entity_configuration
92
+ Federails::Configuration.entity_types[entity.class.name]
93
+ end
94
+
95
+ class << self
96
+ def find_by_account(account) # rubocop:todo Metrics/AbcSize
97
+ parts = Fediverse::Webfinger.split_account account
98
+
99
+ if Fediverse::Webfinger.local_user? parts
100
+ actor = nil
101
+ Federails::Configuration.entity_types.each_value do |entity|
102
+ actor ||= entity[:class].find_by(entity[:username_field] => parts[:username])&.actor
103
+ end
104
+ raise ActiveRecord::RecordNotFound if actor.nil?
105
+ else
106
+ actor = find_by username: parts[:username], server: parts[:domain]
107
+ actor ||= Fediverse::Webfinger.fetch_actor(parts[:username], parts[:domain])
108
+ end
109
+
110
+ actor
111
+ end
112
+
113
+ def find_by_federation_url(federated_url)
114
+ local_route = Utils::Host.local_route federated_url
115
+ return find local_route[:id] if local_route && local_route[:controller] == 'federails/server/actors' && local_route[:action] == 'show'
116
+
117
+ actor = find_by federated_url: federated_url
118
+ return actor if actor
119
+
120
+ Fediverse::Webfinger.fetch_actor_url(federated_url)
121
+ end
122
+
123
+ def find_or_create_by_account(account)
124
+ actor = find_by_account account
125
+ # Create/update distant actors
126
+ actor.save! unless actor.local?
127
+
128
+ actor
129
+ end
130
+
131
+ def find_or_create_by_federation_url(url)
132
+ actor = find_by_federation_url url
133
+ # Create/update distant actors
134
+ actor.save! unless actor.local?
135
+
136
+ actor
137
+ end
138
+
139
+ # Find or create actor from a given actor hash or actor id (actor's URL)
140
+ def find_or_create_by_object(object)
141
+ case object
142
+ when String
143
+ find_or_create_by_federation_url object
144
+ when Hash
145
+ find_or_create_by_federation_url object['id']
146
+ else
147
+ raise "Unsupported object type for actor (#{object.class})"
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,43 @@
1
+ module Federails
2
+ class Following < ApplicationRecord
3
+ enum status: { pending: 0, accepted: 1 }
4
+
5
+ validates :target_actor_id, uniqueness: { scope: [:actor_id, :target_actor_id] }
6
+
7
+ belongs_to :actor
8
+ belongs_to :target_actor, class_name: 'Federails::Actor'
9
+ # FIXME: Handle this with something like undelete
10
+ has_many :activities, as: :entity, dependent: :destroy
11
+
12
+ after_create :create_activity
13
+ after_destroy :destroy_activity
14
+
15
+ scope :with_actor, ->(actor) { where(actor_id: actor.id).or(where(target_actor_id: actor.id)) }
16
+
17
+ def federated_url
18
+ attributes['federated_url'].presence || Federails::Engine.routes.url_helpers.server_actor_following_url(actor_id: actor_id, id: id)
19
+ end
20
+
21
+ def accept!
22
+ update! status: :accepted
23
+ Activity.create! actor: target_actor, action: 'Accept', entity: self
24
+ end
25
+
26
+ class << self
27
+ def new_from_account(account, actor:)
28
+ target_actor = Actor.find_or_create_by_account account
29
+ new actor: actor, target_actor: target_actor
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def create_activity
36
+ Activity.create! actor: actor, action: 'Create', entity: self
37
+ end
38
+
39
+ def destroy_activity
40
+ Activity.create! actor: actor, action: 'Undo', entity: self
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,6 @@
1
+ module Federails
2
+ module Client
3
+ class ActivityPolicy < Federails::FederailsPolicy
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ module Federails
2
+ module Client
3
+ class ActorPolicy < Federails::FederailsPolicy
4
+ def lookup?
5
+ true
6
+ end
7
+
8
+ class Scope < Scope
9
+ def resolve
10
+ scope.local
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ module Federails
2
+ module Client
3
+ class FollowingPolicy < Federails::FederailsPolicy
4
+ def show?
5
+ in_following?
6
+ end
7
+
8
+ def destroy?
9
+ in_following?
10
+ end
11
+
12
+ def accept?
13
+ in_following? && @record.target_actor_id == @user.actor.id
14
+ end
15
+
16
+ def follow?
17
+ create?
18
+ end
19
+
20
+ class Scope < Scope
21
+ def resolve
22
+ scope.with_actor(@user.actor)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def in_following?
29
+ return false if @user.blank?
30
+
31
+ @record.actor_id == @user.actor.id || @record.target_actor_id == @user.actor.id
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,59 @@
1
+ module Federails
2
+ class FederailsPolicy
3
+ attr_reader :user, :record
4
+
5
+ def initialize(user, record)
6
+ @user = user
7
+ @record = record
8
+ end
9
+
10
+ def index?
11
+ true
12
+ end
13
+
14
+ def show?
15
+ true
16
+ end
17
+
18
+ def create?
19
+ @user.present?
20
+ end
21
+
22
+ def new?
23
+ create?
24
+ end
25
+
26
+ def update?
27
+ owner?
28
+ end
29
+
30
+ def edit?
31
+ update?
32
+ end
33
+
34
+ def destroy?
35
+ owner?
36
+ end
37
+
38
+ class Scope
39
+ attr_reader :user, :scope
40
+
41
+ def initialize(user, scope)
42
+ @user = user
43
+ @scope = scope
44
+ end
45
+
46
+ def resolve
47
+ scope.all
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def owner?
54
+ return false unless @user
55
+
56
+ @record.actor_id == @user.actor.id
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,6 @@
1
+ module Federails
2
+ module Server
3
+ class ActivityPolicy < Federails::FederailsPolicy
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,23 @@
1
+ module Federails
2
+ module Server
3
+ class ActorPolicy < Federails::FederailsPolicy
4
+ def show?
5
+ @record.local?
6
+ end
7
+
8
+ def following?
9
+ true
10
+ end
11
+
12
+ def followers?
13
+ true
14
+ end
15
+
16
+ class Scope < Scope
17
+ def resolve
18
+ scope.local
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,6 @@
1
+ module Federails
2
+ module Server
3
+ class FollowingPolicy < Federails::FederailsPolicy
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ <div>
2
+ <b><%= activity.actor.name %></b>
3
+ <code><%= activity.action %></code>
4
+ <!-- < %= link_to activity.entity_type, activity.entity %>-->
5
+ </div>
@@ -0,0 +1 @@
1
+ json.extract! activity, :id, :entity_id, :entity_type, :action, :actor_id, :created_at
@@ -0,0 +1 @@
1
+ json.array! @activities, partial: 'federails/client/activities/activity', as: :activity
@@ -0,0 +1,4 @@
1
+ <h1>Your feed !</h1>
2
+ <% @activities.each do |activity| %>
3
+ <%= render 'activity', activity: activity %>
4
+ <% end %>
@@ -0,0 +1 @@
1
+ json.partial! 'federails/client/activities/index'
@@ -0,0 +1,5 @@
1
+ <h1>Listing activities</h1>
2
+
3
+ <%= @activities.each do |activity| %>
4
+ <%= render 'activity', activity: activity %>
5
+ <% end %>
@@ -0,0 +1 @@
1
+ json.partial! 'federails/client/activities/index'
@@ -0,0 +1,14 @@
1
+ json.extract! actor,
2
+ :id,
3
+ :name,
4
+ :federated_url,
5
+ :username,
6
+ :inbox_url,
7
+ :outbox_url,
8
+ :followers_url,
9
+ :followings_url,
10
+ :profile_url,
11
+ :at_address,
12
+ :user_id,
13
+ :created_at,
14
+ :updated_at
@@ -0,0 +1,5 @@
1
+ <%= form_tag federails.lookup_client_actors_url, method: :get do %>
2
+ <%= label_tag :account %>
3
+ <%= text_field_tag :account, nil, placeholder: 'user@domain.tld or username', required: true %>
4
+ <%= submit_tag 'Search' %>
5
+ <% end %>
@@ -0,0 +1,24 @@
1
+ <h1>Listing actors</h1>
2
+
3
+ <table>
4
+ <thead>
5
+ <tr>
6
+ <th>Name</th>
7
+ <th>Username</th>
8
+ <th>Federation address</th>
9
+ <th>Local?</th>
10
+ <th></th>
11
+ </tr>
12
+ </thead>
13
+ <tbody>
14
+ <% @actors.each do |actor| %>
15
+ <tr>
16
+ <td><%= actor.name %></td>
17
+ <td><%= actor.username %></td>
18
+ <td><%= actor.at_address %></td>
19
+ <td><%= actor.local? %></td>
20
+ <td><%= link_to 'Show', federails.client_actor_url(actor) %></td>
21
+ </tr>
22
+ <% end %>
23
+ </tbody>
24
+ </table>
@@ -0,0 +1 @@
1
+ json.array! @actors, partial: 'federails/client/actors/actor', as: :actor
@@ -0,0 +1,100 @@
1
+ <h2><%= @actor.name %></h2>
2
+
3
+ <% if Federails::Client::FollowingPolicy.new(current_user, Federails::Following).create? %>
4
+ <p>
5
+ <%
6
+ follow = current_user.actor.follows? @actor
7
+ if @actor.entity == current_user
8
+ %>
9
+ <button role="button" disabled="disabled">That's you</button>
10
+ <% elsif follow %>
11
+ Already following (<%= follow.status %>)
12
+ <%= button_to 'Revoke', federails.client_following_path(follow), method: :delete %>
13
+ <% else %>
14
+ <%= button_to 'Follow!', federails.follow_client_followings_path, params: { account: @actor.at_address }, method: :post %>
15
+ <% end %>
16
+ </p>
17
+ <% end %>
18
+
19
+ <p>
20
+ <b>Federated url:</b>
21
+ <%= @actor.federated_url %>
22
+ </p>
23
+
24
+ <p>
25
+ <b>Username:</b>
26
+ <%= @actor.username %>
27
+ </p>
28
+
29
+ <p>
30
+ <b>Inbox URL:</b>
31
+ <%= @actor.inbox_url %>
32
+ </p>
33
+
34
+ <p>
35
+ <b>Outbox URL:</b>
36
+ <%= @actor.outbox_url %>
37
+ </p>
38
+
39
+ <p>
40
+ <b>Followers URL:</b>
41
+ <%= @actor.followers_url %>
42
+ </p>
43
+
44
+ <p>
45
+ <b>Followings URL:</b>
46
+ <%= @actor.followings_url %>
47
+ </p>
48
+
49
+ <p>
50
+ <b>Profile url:</b>
51
+ <% if @actor.profile_url %>
52
+ <%= link_to 'Profile', @actor.profile_url %>
53
+ <% end %>
54
+ </p>
55
+
56
+ <p>
57
+ <b>Federation address:</b>
58
+ <%= @actor.at_address %>
59
+ </p>
60
+
61
+ <p>
62
+ <b>Home page:</b>
63
+ <% if @actor.local? && Federails::Configuration.user_profile_url_method %>
64
+ <%= link_to @actor.send(Federails::Configuration.user_username_field), send(Federails::Configuration.user_profile_url_method, @actor.user) %>
65
+ <% elsif @actor.profile_url %>
66
+ <%= link_to @actor.name, @actor.profile_url %>
67
+ <% else %>
68
+ (No homepage)
69
+ <% end %>
70
+ </p>
71
+
72
+ <hr>
73
+
74
+ <h2>Follows</h2>
75
+ <% if current_user && current_user == @actor.entity %>
76
+ <%= render 'federails/client/followings/form', following: Federails::Following.new %>
77
+ <% end %>
78
+
79
+ <% @actor.following_follows.each do |following| %>
80
+ <%= render 'federails/client/followings/follow', following: following %>
81
+ <% end %>
82
+
83
+ <h2>Followers</h2>
84
+ <% @actor.following_followers.each do |following| %>
85
+ <%= render 'federails/client/followings/follower', following: following %>
86
+ <% end %>
87
+
88
+ <%= link_to 'Back', federails.client_actors_url %>
89
+
90
+ <!-- FIXME: Fetch distant content somehow -->
91
+ <h2>10 last activities</h2>
92
+ <% if @actor.local? %>
93
+ <%= link_to 'All', federails.client_actor_activities_path(@actor) %>
94
+ <% elsif @actor.profile_url %>
95
+ <%= link_to 'Visit profile', @actor.profile_url %>
96
+ <% end %>
97
+
98
+ <% @actor.activities.last(10).each do |activity| %>
99
+ <%= render 'federails/client/activities/activity', activity: activity %>
100
+ <% end %>
@@ -0,0 +1 @@
1
+ json.partial! 'federails/client/actors/actor', actor: @actor
@@ -0,0 +1,4 @@
1
+ <div>
2
+ <b><%= link_to following.target_actor.name, federails.client_actor_url(following.target_actor) %></b>
3
+ (<%= following.target_actor.at_address %>) (<%= following.status %>)
4
+ </div>
@@ -0,0 +1,7 @@
1
+ <div>
2
+ <b><%= link_to following.actor.name, federails.client_actor_url(following.actor) %></b>
3
+ (<%= following.actor.at_address %>) (<%= following.status %>)
4
+ <% if following.pending? && following.target_actor == current_user.actor %>
5
+ <%= button_to 'Accept', federails.accept_client_following_path(following), method: :put %>
6
+ <% end %>
7
+ </div>
@@ -0,0 +1 @@
1
+ json.extract! following, :id, :actor_id, :target_actor_id, :status, :created_at, :updated_at
@@ -0,0 +1,21 @@
1
+ <%= form_for following, url: federails.client_following_url do |f| %>
2
+ <% if following.errors.any? %>
3
+ <div class="error_explanation">
4
+ <h2><%= pluralize(following.errors.count, 'error') %> prohibited this following from being saved:</h2>
5
+ <ul>
6
+ <% following.errors.full_messages.each do |message| %>
7
+ <li><%= message %></li>
8
+ <% end %>
9
+ </ul>
10
+ </div>
11
+ <% end %>
12
+
13
+ <div class="field">
14
+ <%= f.label :target_actor %>
15
+ <%= f.collection_select :target_actor_id, Federails::Actor.all, :id, :name %>
16
+ </div>
17
+
18
+ <div class="actions">
19
+ <%= f.submit 'Save' %>
20
+ </div>
21
+ <% end %>
@@ -0,0 +1,29 @@
1
+ <h1>Listing followings</h1>
2
+
3
+ <table>
4
+ <thead>
5
+ <tr>
6
+ <th>Actor</th>
7
+ <th>Target actor</th>
8
+ <th>Status</th>
9
+ <th></th>
10
+ <th></th>
11
+ <th></th>
12
+ </tr>
13
+ </thead>
14
+
15
+ <tbody>
16
+ <% @followings.each do |following| %>
17
+ <tr>
18
+ <td><%= following.actor %></td>
19
+ <td><%= following.target_actor %></td>
20
+ <td><%= following.status %></td>
21
+ <td><%= link_to 'Show', following %></td>
22
+ <td><%= link_to 'Edit', edit_following_path(following) %></td>
23
+ <td><%= link_to 'Destroy', following, method: :delete, data: { confirm: 'Are you sure?' } %></td>
24
+ </tr>
25
+ <% end %>
26
+ </tbody>
27
+ </table>
28
+
29
+ <%= link_to 'New Following', new_following_path %>
@@ -0,0 +1 @@
1
+ json.array! @followings, partial: 'federails/client/followings/following', as: :following
@@ -0,0 +1,21 @@
1
+ <p id="notice"><%= notice %></p>
2
+
3
+ <p>
4
+ <b>Actor:</b>
5
+ <%= @following.actor %>
6
+ </p>
7
+
8
+ <p>
9
+ <b>Target actor:</b>
10
+ <%= @following.target_actor %>
11
+ </p>
12
+
13
+ <p>
14
+ <b>Status:</b>
15
+ <%= @following.status %>
16
+ </p>
17
+
18
+
19
+ <%= link_to 'Edit', edit_following_path(@following) %>
20
+ \|
21
+ <%= link_to 'Back', followings_path %>