activitypub: user view: use route helpers instead of hardcoded URIs
[akkoma] / lib / pleroma / web / activity_pub / views / user_view.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.UserView do
6 use Pleroma.Web, :view
7
8 alias Pleroma.Web.WebFinger
9 alias Pleroma.Web.Salmon
10 alias Pleroma.User
11 alias Pleroma.Repo
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.Transmogrifier
14 alias Pleroma.Web.ActivityPub.Utils
15 alias Pleroma.Web.Router.Helpers
16 alias Pleroma.Web.Endpoint
17
18 import Ecto.Query
19
20 def render("endpoints.json", %{user: %User{nickname: _nickname, local: true} = _user}) do
21 %{
22 "oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize),
23 "oauthRegistrationEndpoint" => Helpers.mastodon_api_url(Endpoint, :create_app),
24 "oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
25 "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)
26 }
27 end
28
29 def render("endpoints.json", _), do: %{}
30
31 # the instance itself is not a Person, but instead an Application
32 def render("user.json", %{user: %{nickname: nil} = user}) do
33 {:ok, user} = WebFinger.ensure_keys_present(user)
34 {:ok, _, public_key} = Salmon.keys_from_pem(user.info.keys)
35 public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
36 public_key = :public_key.pem_encode([public_key])
37
38 endpoints = render("endpoints.json", %{user: user})
39
40 %{
41 "id" => user.ap_id,
42 "type" => "Application",
43 "following" => "#{user.ap_id}/following",
44 "followers" => "#{user.ap_id}/followers",
45 "inbox" => "#{user.ap_id}/inbox",
46 "name" => "Pleroma",
47 "summary" => "Virtual actor for Pleroma relay",
48 "url" => user.ap_id,
49 "manuallyApprovesFollowers" => false,
50 "publicKey" => %{
51 "id" => "#{user.ap_id}#main-key",
52 "owner" => user.ap_id,
53 "publicKeyPem" => public_key
54 },
55 "endpoints" => endpoints
56 }
57 |> Map.merge(Utils.make_json_ld_header())
58 end
59
60 def render("user.json", %{user: user}) do
61 {:ok, user} = WebFinger.ensure_keys_present(user)
62 {:ok, _, public_key} = Salmon.keys_from_pem(user.info.keys)
63 public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
64 public_key = :public_key.pem_encode([public_key])
65
66 endpoints = render("endpoints.json", %{user: user})
67
68 %{
69 "id" => user.ap_id,
70 "type" => "Person",
71 "following" => "#{user.ap_id}/following",
72 "followers" => "#{user.ap_id}/followers",
73 "inbox" => "#{user.ap_id}/inbox",
74 "outbox" => "#{user.ap_id}/outbox",
75 "preferredUsername" => user.nickname,
76 "name" => user.name,
77 "summary" => user.bio,
78 "url" => user.ap_id,
79 "manuallyApprovesFollowers" => user.info.locked,
80 "publicKey" => %{
81 "id" => "#{user.ap_id}#main-key",
82 "owner" => user.ap_id,
83 "publicKeyPem" => public_key
84 },
85 "endpoints" => endpoints,
86 "icon" => %{
87 "type" => "Image",
88 "url" => User.avatar_url(user)
89 },
90 "image" => %{
91 "type" => "Image",
92 "url" => User.banner_url(user)
93 },
94 "tag" => user.info.source_data["tag"] || []
95 }
96 |> Map.merge(Utils.make_json_ld_header())
97 end
98
99 def render("following.json", %{user: user, page: page}) do
100 query = User.get_friends_query(user)
101 query = from(user in query, select: [:ap_id])
102 following = Repo.all(query)
103
104 collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows)
105 |> Map.merge(Utils.make_json_ld_header())
106 end
107
108 def render("following.json", %{user: user}) do
109 query = User.get_friends_query(user)
110 query = from(user in query, select: [:ap_id])
111 following = Repo.all(query)
112
113 %{
114 "id" => "#{user.ap_id}/following",
115 "type" => "OrderedCollection",
116 "totalItems" => length(following),
117 "first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
118 }
119 |> Map.merge(Utils.make_json_ld_header())
120 end
121
122 def render("followers.json", %{user: user, page: page}) do
123 query = User.get_followers_query(user)
124 query = from(user in query, select: [:ap_id])
125 followers = Repo.all(query)
126
127 collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers)
128 |> Map.merge(Utils.make_json_ld_header())
129 end
130
131 def render("followers.json", %{user: user}) do
132 query = User.get_followers_query(user)
133 query = from(user in query, select: [:ap_id])
134 followers = Repo.all(query)
135
136 %{
137 "id" => "#{user.ap_id}/followers",
138 "type" => "OrderedCollection",
139 "totalItems" => length(followers),
140 "first" => collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers)
141 }
142 |> Map.merge(Utils.make_json_ld_header())
143 end
144
145 def render("outbox.json", %{user: user, max_id: max_qid}) do
146 # XXX: technically note_count is wrong for this, but it's better than nothing
147 info = User.user_info(user)
148
149 params = %{
150 "limit" => "10"
151 }
152
153 params =
154 if max_qid != nil do
155 Map.put(params, "max_id", max_qid)
156 else
157 params
158 end
159
160 activities = ActivityPub.fetch_user_activities(user, nil, params)
161 min_id = Enum.at(Enum.reverse(activities), 0).id
162 max_id = Enum.at(activities, 0).id
163
164 collection =
165 Enum.map(activities, fn act ->
166 {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
167 data
168 end)
169
170 iri = "#{user.ap_id}/outbox"
171
172 page = %{
173 "id" => "#{iri}?max_id=#{max_id}",
174 "type" => "OrderedCollectionPage",
175 "partOf" => iri,
176 "totalItems" => info.note_count,
177 "orderedItems" => collection,
178 "next" => "#{iri}?max_id=#{min_id}"
179 }
180
181 if max_qid == nil do
182 %{
183 "id" => iri,
184 "type" => "OrderedCollection",
185 "totalItems" => info.note_count,
186 "first" => page
187 }
188 |> Map.merge(Utils.make_json_ld_header())
189 else
190 page |> Map.merge(Utils.make_json_ld_header())
191 end
192 end
193
194 def render("inbox.json", %{user: user, max_id: max_qid}) do
195 params = %{
196 "limit" => "10"
197 }
198
199 params =
200 if max_qid != nil do
201 Map.put(params, "max_id", max_qid)
202 else
203 params
204 end
205
206 activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
207
208 min_id = Enum.at(Enum.reverse(activities), 0).id
209 max_id = Enum.at(activities, 0).id
210
211 collection =
212 Enum.map(activities, fn act ->
213 {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
214 data
215 end)
216
217 iri = "#{user.ap_id}/inbox"
218
219 page = %{
220 "id" => "#{iri}?max_id=#{max_id}",
221 "type" => "OrderedCollectionPage",
222 "partOf" => iri,
223 "totalItems" => -1,
224 "orderedItems" => collection,
225 "next" => "#{iri}?max_id=#{min_id}"
226 }
227
228 if max_qid == nil do
229 %{
230 "id" => iri,
231 "type" => "OrderedCollection",
232 "totalItems" => -1,
233 "first" => page
234 }
235 |> Map.merge(Utils.make_json_ld_header())
236 else
237 page |> Map.merge(Utils.make_json_ld_header())
238 end
239 end
240
241 def collection(collection, iri, page, show_items \\ true, total \\ nil) do
242 offset = (page - 1) * 10
243 items = Enum.slice(collection, offset, 10)
244 items = Enum.map(items, fn user -> user.ap_id end)
245 total = total || length(collection)
246
247 map = %{
248 "id" => "#{iri}?page=#{page}",
249 "type" => "OrderedCollectionPage",
250 "partOf" => iri,
251 "totalItems" => total,
252 "orderedItems" => if(show_items, do: items, else: [])
253 }
254
255 if offset < total do
256 Map.put(map, "next", "#{iri}?page=#{page + 1}")
257 else
258 map
259 end
260 end
261 end