mastodon_api_controller.ex 20.8 KB
Newer Older
1 2
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
  use Pleroma.Web, :controller
3
  alias Pleroma.{Repo, Activity, User, Notification, Stats}
lain's avatar
lain committed
4
  alias Pleroma.Web
lain's avatar
lain committed
5
  alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView}
lain's avatar
lain committed
6
  alias Pleroma.Web.ActivityPub.ActivityPub
7
  alias Pleroma.Web.{CommonAPI, OStatus}
lain's avatar
lain committed
8 9
  alias Pleroma.Web.OAuth.{Authorization, Token, App}
  alias Comeonin.Pbkdf2
Roger Braun's avatar
Roger Braun committed
10
  import Ecto.Query
Thog's avatar
Thog committed
11
  require Logger
12 13 14 15 16 17 18 19 20 21 22 23 24 25

  def create_app(conn, params) do
    with cs <- App.register_changeset(%App{}, params) |> IO.inspect,
         {:ok, app} <- Repo.insert(cs) |> IO.inspect do
      res = %{
        id: app.id,
        client_id: app.client_id,
        client_secret: app.client_secret
      }

      json(conn, res)
    end
  end

26
  def update_credentials(%{assigns: %{user: user}} = conn, params) do
lain's avatar
lain committed
27
    original_user = user
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
    params = if bio = params["note"] do
      Map.put(params, "bio", bio)
    else
      params
    end

    params = if name = params["display_name"] do
      Map.put(params, "name", name)
    else
      params
    end

    user = if avatar = params["avatar"] do
      with %Plug.Upload{} <- avatar,
           {:ok, object} <- ActivityPub.upload(avatar),
           change = Ecto.Changeset.change(user, %{avatar: object.data}),
lain's avatar
lain committed
44
           {:ok, user} = User.update_and_set_cache(change) do
45 46 47 48 49 50 51 52 53 54 55 56 57
        user
      else
        _e -> user
      end
    else
      user
    end

    user = if banner = params["header"] do
      with %Plug.Upload{} <- banner,
           {:ok, object} <- ActivityPub.upload(banner),
           new_info <- Map.put(user.info, "banner", object.data),
           change <- User.info_changeset(user, %{info: new_info}),
lain's avatar
lain committed
58
           {:ok, user} <- User.update_and_set_cache(change) do
59 60 61 62 63 64 65 66 67
        user
      else
        _e -> user
      end
    else
      user
    end

    with changeset <- User.update_changeset(user, params),
lain's avatar
lain committed
68 69 70 71
         {:ok, user} <- User.update_and_set_cache(changeset) do
      if original_user != user do
        CommonAPI.update(user)
      end
72 73 74 75 76 77 78 79 80
      json conn, AccountView.render("account.json", %{user: user})
    else
      _e ->
        conn
        |> put_status(403)
        |> json(%{error: "Invalid request"})
    end
  end

Thog's avatar
Thog committed
81
  def verify_credentials(%{assigns: %{user: user}} = conn, _) do
lain's avatar
lain committed
82 83 84 85
    account = AccountView.render("account.json", %{user: user})
    json(conn, account)
  end

Roger Braun's avatar
Roger Braun committed
86 87 88 89 90 91 92 93 94 95 96
  def user(conn, %{"id" => id}) do
    with %User{} = user <- Repo.get(User, id) do
      account = AccountView.render("account.json", %{user: user})
      json(conn, account)
    else
      _e -> conn
      |> put_status(404)
      |> json(%{error: "Can't find user"})
    end
  end

lain's avatar
lain committed
97 98
  @instance Application.get_env(:pleroma, :instance)

lain's avatar
lain committed
99 100 101
  def masto_instance(conn, _params) do
    response = %{
      uri: Web.base_url,
lain's avatar
lain committed
102
      title: Keyword.get(@instance, :name),
lain's avatar
lain committed
103
      description: "A Pleroma instance, an alternative fediverse server",
lain's avatar
lain committed
104 105 106 107 108
      version: Keyword.get(@instance, :version),
      email: Keyword.get(@instance, :email),
      urls: %{
        streaming_api: String.replace(Web.base_url, ["http","https"], "wss")
      },
eal's avatar
eal committed
109
      stats: Stats.get_stats,
110
      thumbnail: Web.base_url <> "/instance/thumbnail.jpeg",
lain's avatar
lain committed
111
      max_toot_chars: Keyword.get(@instance, :limit)
112 113
    }

lain's avatar
lain committed
114
    json(conn, response)
115
  end
lain's avatar
lain committed
116

117
  def peers(conn, _params) do
eal's avatar
eal committed
118
    json(conn, Stats.get_peers)
119 120
  end

121 122
  defp mastodonized_emoji do
    Pleroma.Formatter.get_custom_emoji()
123 124 125 126 127 128 129 130
    |> Enum.map(fn {shortcode, relative_url} ->
      url = to_string URI.merge(Web.base_url(), relative_url)
      %{
        "shortcode" => shortcode,
        "static_url" => url,
        "url" => url
      }
    end)
131 132 133 134
  end

  def custom_emojis(conn, _params) do
    mastodon_emoji = mastodonized_emoji()
135 136 137
    json conn, mastodon_emoji
  end

lain's avatar
lain committed
138
  defp add_link_headers(conn, method, activities) do
139 140 141 142 143
    last = List.last(activities)
    first = List.first(activities)
    if last do
      min = last.id
      max = first.id
lain's avatar
lain committed
144 145
      next_url = mastodon_api_url(Pleroma.Web.Endpoint, method, max_id: min)
      prev_url = mastodon_api_url(Pleroma.Web.Endpoint, method, since_id: max)
146 147 148 149 150 151 152
      conn
      |> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
    else
      conn
    end
  end

lain's avatar
lain committed
153
  def home_timeline(%{assigns: %{user: user}} = conn, params) do
lain's avatar
lain committed
154 155
    params = params
    |> Map.put("type", ["Create", "Announce"])
lain's avatar
lain committed
156
    |> Map.put("blocking_user", user)
lain's avatar
lain committed
157
    |> Map.put("user", user)
lain's avatar
lain committed
158 159

    activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
lain's avatar
lain committed
160
    |> Enum.reverse
161 162

    conn
lain's avatar
lain committed
163
    |> add_link_headers(:home_timeline, activities)
lain's avatar
lain committed
164
    |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
lain's avatar
lain committed
165 166 167 168
  end

  def public_timeline(%{assigns: %{user: user}} = conn, params) do
    params = params
lain's avatar
lain committed
169
    |> Map.put("type", ["Create", "Announce"])
lain's avatar
lain committed
170
    |> Map.put("local_only", params["local"] in [true, "True", "true", "1"])
lain's avatar
lain committed
171
    |> Map.put("blocking_user", user)
lain's avatar
lain committed
172 173

    activities = ActivityPub.fetch_public_activities(params)
lain's avatar
lain committed
174
    |> Enum.reverse
lain's avatar
lain committed
175

lain's avatar
lain committed
176 177 178
    conn
    |> add_link_headers(:public_timeline, activities)
    |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
lain's avatar
lain committed
179 180
  end

181
  # TODO: Link headers
lain's avatar
lain committed
182 183 184
  def user_statuses(%{assigns: %{user: user}} = conn, params) do
    with %User{ap_id: ap_id} <- Repo.get(User, params["id"]) do
      params = params
lain's avatar
lain committed
185
      |> Map.put("type", ["Create", "Announce"])
lain's avatar
lain committed
186
      |> Map.put("actor_id", ap_id)
187
      |> Map.put("whole_db", true)
lain's avatar
lain committed
188

lain's avatar
lain committed
189
      activities = ActivityPub.fetch_public_activities(params)
lain's avatar
lain committed
190 191 192 193 194 195
      |> Enum.reverse

      render conn, StatusView, "index.json", %{activities: activities, for: user, as: :activity}
    end
  end

lain's avatar
lain committed
196
  def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
lain's avatar
lain committed
197 198
    with %Activity{} = activity <- Repo.get(Activity, id),
         true <- ActivityPub.visible_for_user?(activity, user) do
lain's avatar
lain committed
199 200 201 202
      render conn, StatusView, "status.json", %{activity: activity, for: user}
    end
  end

lain's avatar
lain committed
203 204
  def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
    with %Activity{} = activity <- Repo.get(Activity, id),
lain's avatar
lain committed
205
         activities <- ActivityPub.fetch_activities_for_context(activity.data["object"]["context"], %{"blocking_user" => user, "user" => user}),
lain's avatar
lain committed
206
         activities <- activities |> Enum.filter(fn (%{id: aid}) -> to_string(aid) != to_string(id) end),
207
         activities <- activities |> Enum.filter(fn (%{data: %{"type" => type}}) -> type == "Create" end),
lain's avatar
lain committed
208
         grouped_activities <- Enum.group_by(activities, fn (%{id: id}) -> id < activity.id end) do
lain's avatar
lain committed
209
      result = %{
lain's avatar
lain committed
210 211
        ancestors: StatusView.render("index.json", for: user, activities: grouped_activities[true] || [], as: :activity) |> Enum.reverse,
        descendants: StatusView.render("index.json", for: user, activities: grouped_activities[false] || [], as: :activity) |> Enum.reverse,
lain's avatar
lain committed
212 213 214 215 216 217
      }

      json(conn, result)
    end
  end

Thog's avatar
Thog committed
218
  def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
lain's avatar
lain committed
219 220
    params = params
    |> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
eal's avatar
eal committed
221
    |> Map.put("no_attachment_links", true)
lain's avatar
lain committed
222

223 224
    {:ok, activity} = CommonAPI.post(user, params)
    render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}
lain's avatar
lain committed
225
  end
lain's avatar
lain committed
226 227 228 229 230 231 232 233 234 235 236

  def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
    with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
      json(conn, %{})
    else
      _e ->
        conn
        |> put_status(403)
        |> json(%{error: "Can't delete this post"})
    end
  end
lain's avatar
lain committed
237 238

  def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
lain's avatar
lain committed
239 240
    with {:ok, announce, _activity} = CommonAPI.repeat(ap_id_or_id, user) do
      render conn, StatusView, "status.json", %{activity: announce, for: user, as: :activity}
lain's avatar
lain committed
241 242
    end
  end
lain's avatar
lain committed
243 244

  def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
lain's avatar
lain committed
245 246 247 248 249 250 251 252
    with {:ok, _fav, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user),
         %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
      render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}
    end
  end

  def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
    with {:ok, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user),
lain's avatar
lain committed
253 254 255 256
         %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
      render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}
    end
  end
257

258 259
  def notifications(%{assigns: %{user: user}} = conn, params) do
    notifications = Notification.for_user(user, params)
260 261
    result = Enum.map(notifications, fn x ->
      render_notification(user, x)
262 263 264
    end)
    |> Enum.filter(&(&1))

lain's avatar
lain committed
265 266 267
    conn
    |> add_link_headers(:notifications, notifications)
    |> json(result)
268 269
  end

270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
  def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
    with {:ok, notification} <- Notification.get(user, id) do
      json(conn, render_notification(user, notification))
    else
      {:error, reason} ->
        conn
        |> put_resp_content_type("application/json")
        |> send_resp(403, Poison.encode!(%{"error" => reason}))
    end
  end

  def clear_notifications(%{assigns: %{user: user}} = conn, _params) do
    Notification.clear(user)
    json(conn, %{})
  end

  def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
    with {:ok, _notif} <- Notification.dismiss(user, id) do
      json(conn, %{})
    else
      {:error, reason} ->
        conn
        |> put_resp_content_type("application/json")
        |> send_resp(403, Poison.encode!(%{"error" => reason}))
    end
  end

Roger Braun's avatar
Roger Braun committed
297 298 299 300 301 302 303 304
  def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
    id = List.wrap(id)
    q = from u in User,
      where: u.id in ^id
    targets = Repo.all(q)
    render conn, AccountView, "relationships.json", %{user: user, targets: targets}
  end

Thog's avatar
Thog committed
305
  def upload(%{assigns: %{user: _}} = conn, %{"file" => file}) do
lain's avatar
lain committed
306 307 308 309 310 311 312 313
    with {:ok, object} <- ActivityPub.upload(file) do
      data = object.data
      |> Map.put("id", object.id)

      render conn, StatusView, "attachment.json", %{attachment: data}
    end
  end

314
  def favourited_by(conn, %{"id" => id}) do
Thog's avatar
Thog committed
315
    with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
      q = from u in User,
        where: u.ap_id in ^likes
      users = Repo.all(q)
      render conn, AccountView, "accounts.json", %{users: users, as: :user}
    else
      _ -> json(conn, [])
    end
  end

  def reblogged_by(conn, %{"id" => id}) do
    with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Repo.get(Activity, id) do
      q = from u in User,
        where: u.ap_id in ^announces
      users = Repo.all(q)
      render conn, AccountView, "accounts.json", %{users: users, as: :user}
    else
      _ -> json(conn, [])
    end
  end

336
  # TODO: Link headers
Roger Braun's avatar
Roger Braun committed
337 338 339 340
  def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
    params = params
    |> Map.put("type", "Create")
    |> Map.put("local_only", !!params["local"])
lain's avatar
lain committed
341
    |> Map.put("blocking_user", user)
Roger Braun's avatar
Roger Braun committed
342 343 344 345 346 347 348 349

    activities = ActivityPub.fetch_public_activities(params)
    |> Enum.reverse

    conn
    |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
  end

350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
  # TODO: Pagination
  def followers(conn, %{"id" => id}) do
    with %User{} = user <- Repo.get(User, id),
         {:ok, followers} <- User.get_followers(user) do
      render conn, AccountView, "accounts.json", %{users: followers, as: :user}
    end
  end

  def following(conn, %{"id" => id}) do
    with %User{} = user <- Repo.get(User, id),
         {:ok, followers} <- User.get_friends(user) do
      render conn, AccountView, "accounts.json", %{users: followers, as: :user}
    end
  end

eal's avatar
eal committed
365 366
  def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
    with %User{} = followed <- Repo.get(User, id),
eal's avatar
eal committed
367
         {:ok, follower} <- User.follow(follower, followed),
Thog's avatar
Thog committed
368
         {:ok, _activity} <- ActivityPub.follow(follower, followed) do
369
      render conn, AccountView, "relationship.json", %{user: follower, target: followed}
eal's avatar
eal committed
370
    else
Thog's avatar
Thog committed
371
      {:error, message} ->
eal's avatar
eal committed
372 373 374
        conn
        |> put_resp_content_type("application/json")
        |> send_resp(403, Poison.encode!(%{"error" => message}))
375 376 377
    end
  end

eal's avatar
eal committed
378
  def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
eal's avatar
eal committed
379
    with %User{} = followed <- Repo.get_by(User, nickname: uri),
eal's avatar
eal committed
380
         {:ok, follower} <- User.follow(follower, followed),
Thog's avatar
Thog committed
381
         {:ok, _activity} <- ActivityPub.follow(follower, followed) do
eal's avatar
eal committed
382 383
      render conn, AccountView, "account.json", %{user: followed}
    else
Thog's avatar
Thog committed
384
      {:error, message} ->
eal's avatar
eal committed
385 386 387 388 389 390
        conn
        |> put_resp_content_type("application/json")
        |> send_resp(403, Poison.encode!(%{"error" => message}))
    end
  end

391 392 393 394 395 396 397 398 399 400 401 402 403
  # TODO: Clean up and unify
  def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
    with %User{} = followed <- Repo.get(User, id),
         { :ok, follower, follow_activity } <- User.unfollow(follower, followed),
         { :ok, _activity } <- ActivityPub.insert(%{
           "type" => "Undo",
           "actor" => follower.ap_id,
           "object" => follow_activity.data["id"] # get latest Follow for these users
         }) do
      render conn, AccountView, "relationship.json", %{user: follower, target: followed}
    end
  end

lain's avatar
lain committed
404 405 406 407 408
  def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
    with %User{} = blocked <- Repo.get(User, id),
         {:ok, blocker} <- User.block(blocker, blocked) do
      render conn, AccountView, "relationship.json", %{user: blocker, target: blocked}
    else
Thog's avatar
Thog committed
409
      {:error, message} ->
lain's avatar
lain committed
410 411 412 413 414 415 416 417 418 419 420
        conn
        |> put_resp_content_type("application/json")
        |> send_resp(403, Poison.encode!(%{"error" => message}))
    end
  end

  def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
    with %User{} = blocked <- Repo.get(User, id),
         {:ok, blocker} <- User.unblock(blocker, blocked) do
      render conn, AccountView, "relationship.json", %{user: blocker, target: blocked}
    else
Thog's avatar
Thog committed
421
      {:error, message} ->
lain's avatar
lain committed
422 423 424 425 426 427
        conn
        |> put_resp_content_type("application/json")
        |> send_resp(403, Poison.encode!(%{"error" => message}))
    end
  end

lain's avatar
lain committed
428 429 430 431 432 433 434 435 436
  # TODO: Use proper query
  def blocks(%{assigns: %{user: user}} = conn, _) do
    with blocked_users <- user.info["blocks"] || [],
         accounts <- Enum.map(blocked_users, fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end) do
      res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
      json(conn, res)
    end
  end

437
  def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
lain's avatar
lain committed
438
    accounts = User.search(query, params["resolve"] == "true")
lain's avatar
lain committed
439

440 441 442 443 444 445 446 447
    fetched = if Regex.match?(~r/https?:/, query) do
      with {:ok, activities} <- OStatus.fetch_activity_from_url(query) do
        activities
      else
        _e -> []
      end
    end || []

lain's avatar
lain committed
448 449
    q = from a in Activity,
      where: fragment("?->>'type' = 'Create'", a.data),
lain's avatar
lain committed
450
      where: fragment("to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", a.data, ^query),
lain's avatar
lain committed
451
      limit: 20
452
    statuses = Repo.all(q) ++ fetched
lain's avatar
lain committed
453 454 455 456 457 458 459 460 461 462

    res = %{
      "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
      "statuses" => StatusView.render("index.json", activities: statuses, for: user, as: :activity),
      "hashtags" => []
    }

    json(conn, res)
  end

lain's avatar
lain committed
463 464
  def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
    accounts = User.search(query, params["resolve"] == "true")
465 466 467 468 469 470

    res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)

    json(conn, res)
  end

Thog's avatar
Thog committed
471
  def favourites(%{assigns: %{user: user}} = conn, _) do
lain's avatar
lain committed
472
    params = %{}
473 474
    |> Map.put("type", "Create")
    |> Map.put("favorited_by", user.ap_id)
lain's avatar
lain committed
475
    |> Map.put("blocking_user", user)
476

lain's avatar
lain committed
477
    activities = ActivityPub.fetch_public_activities(params)
478 479 480 481 482 483
    |> Enum.reverse

    conn
    |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
  end

lain's avatar
lain committed
484 485 486 487 488
  def index(%{assigns: %{user: user}} = conn, _params) do
    token = conn
    |> get_session(:oauth_token)

    if user && token do
489
      mastodon_emoji = mastodonized_emoji()
lain's avatar
lain committed
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
      accounts = Map.put(%{}, user.id, AccountView.render("account.json", %{user: user}))
      initial_state = %{
        meta: %{
          streaming_api_base_url: String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
          access_token: token,
          locale: "en",
          domain: Pleroma.Web.Endpoint.host(),
          admin: "1",
          me: "#{user.id}",
          unfollow_modal: false,
          boost_modal: false,
          delete_modal: true,
          auto_play_gif: false,
          reduce_motion: false
        },
        compose: %{
          me: "#{user.id}",
          default_privacy: "public",
          default_sensitive: false
        },
        media_attachments: %{
          accept_content_types: [
            ".jpg",
            ".jpeg",
            ".png",
            ".gif",
            ".webm",
            ".mp4",
            ".m4v",
            "image\/jpeg",
            "image\/png",
            "image\/gif",
            "video\/webm",
            "video\/mp4"
          ]
        },
        settings: %{
          onboarded: true,
          home: %{
            shows: %{
              reblog: true,
              reply: true
            }
          },
          notifications: %{
            alerts: %{
              follow: true,
              favourite: true,
              reblog: true,
              mention: true
            },
            shows: %{
              follow: true,
              favourite: true,
              reblog: true,
              mention: true
            },
            sounds: %{
              follow: true,
              favourite: true,
              reblog: true,
              mention: true
            }
          }
        },
        push_subscription: nil,
        accounts: accounts,
557
        custom_emojis: mastodon_emoji
lain's avatar
lain committed
558 559 560 561 562 563 564 565 566 567
      } |> Poison.encode!
      conn
      |> put_layout(false)
      |> render(MastodonView, "index.html", %{initial_state: initial_state})
    else
      conn
      |> redirect(to: "/web/login")
    end
  end

Thog's avatar
Thog committed
568
  def login(conn, _) do
lain's avatar
lain committed
569
    conn
570
    |> render(MastodonView, "login.html", %{error: false})
lain's avatar
lain committed
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
  end

  defp get_or_make_app() do
    with %App{} = app <- Repo.get_by(App, client_name: "Mastodon-Local") do
      {:ok, app}
    else
      _e ->
        cs = App.register_changeset(%App{}, %{client_name: "Mastodon-Local", redirect_uris: ".", scopes: "read,write,follow"})
        Repo.insert(cs)
    end
  end

  def login_post(conn, %{"authorization" => %{ "name" => name, "password" => password}}) do
    with %User{} = user <- User.get_cached_by_nickname(name),
         true <- Pbkdf2.checkpw(password, user.password_hash),
         {:ok, app} <- get_or_make_app(),
         {:ok, auth} <- Authorization.create_authorization(app, user),
         {:ok, token} <- Token.exchange_token(app, auth) do
      conn
      |> put_session(:oauth_token, token.token)
eal's avatar
eal committed
591
      |> redirect(to: "/web/getting-started")
592 593 594 595
    else
      _e ->
        conn
        |> render(MastodonView, "login.html", %{error: "Wrong username or password"})
lain's avatar
lain committed
596 597 598
    end
  end

lain's avatar
lain committed
599 600 601 602 603 604
  def logout(conn, _) do
    conn
    |> clear_session
    |> redirect(to: "/")
  end

605 606 607 608 609 610 611
  def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
    Logger.debug("Unimplemented, returning unmodified relationship")
    with %User{} = target <- Repo.get(User, id) do
      render conn, AccountView, "relationship.json", %{user: user, target: target}
    end
  end

612 613 614 615
  def empty_array(conn, _) do
    Logger.debug("Unimplemented, returning an empty array")
    json(conn, [])
  end
616

617
  def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
    actor = User.get_cached_by_ap_id(activity.data["actor"])
    created_at = NaiveDateTime.to_iso8601(created_at)
    |> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
    case activity.data["type"] do
      "Create" ->
        %{id: id, type: "mention", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: activity, for: user})}
      "Like" ->
        liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
        %{id: id, type: "favourite", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: liked_activity, for: user})}
      "Announce" ->
        announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"])
        %{id: id, type: "reblog", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: announced_activity, for: user})}
      "Follow" ->
        %{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})}
      _ -> nil
    end
  end
635
end