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