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