aboutsummaryrefslogtreecommitdiff
path: root/lib/pleroma/pagination.ex
blob: 9d279fba7939f12256d57cf41261f0e506fba562 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Pagination do
  @moduledoc """
  Implements Mastodon-compatible pagination.
  """

  import Ecto.Query
  import Ecto.Changeset

  alias Pleroma.Repo

  @default_limit 20

  def fetch_paginated(query, params, type \\ :keyset)

  def fetch_paginated(query, %{"total" => true} = params, :keyset) do
    total = Repo.aggregate(query, :count, :id)

    %{
      total: total,
      items: fetch_paginated(query, Map.drop(params, ["total"]), :keyset)
    }
  end

  def fetch_paginated(query, params, :keyset) do
    options = cast_params(params)

    query
    |> paginate(options, :keyset)
    |> Repo.all()
    |> enforce_order(options)
  end

  def fetch_paginated(query, %{"total" => true} = params, :offset) do
    total = Repo.aggregate(query, :count, :id)

    %{
      total: total,
      items: fetch_paginated(query, Map.drop(params, ["total"]), :offset)
    }
  end

  def fetch_paginated(query, params, :offset) do
    options = cast_params(params)

    query
    |> paginate(options, :offset)
    |> Repo.all()
  end

  def paginate(query, options, method \\ :keyset)

  def paginate(query, options, :keyset) do
    query
    |> restrict(:min_id, options)
    |> restrict(:since_id, options)
    |> restrict(:max_id, options)
    |> restrict(:order, options)
    |> restrict(:limit, options)
  end

  def paginate(query, options, :offset) do
    query
    |> restrict(:order, options)
    |> restrict(:offset, options)
    |> restrict(:limit, options)
  end

  defp cast_params(params) do
    param_types = %{
      min_id: :string,
      since_id: :string,
      max_id: :string,
      offset: :integer,
      limit: :integer
    }

    params =
      Enum.reduce(params, %{}, fn
        {key, _value}, acc when is_atom(key) -> Map.drop(acc, [key])
        {key, value}, acc -> Map.put(acc, key, value)
      end)

    changeset = cast({%{}, param_types}, params, Map.keys(param_types))
    changeset.changes
  end

  defp restrict(query, :min_id, %{min_id: min_id}) do
    where(query, [q], q.id > ^min_id)
  end

  defp restrict(query, :since_id, %{since_id: since_id}) do
    where(query, [q], q.id > ^since_id)
  end

  defp restrict(query, :max_id, %{max_id: max_id}) do
    where(query, [q], q.id < ^max_id)
  end

  defp restrict(query, :order, %{min_id: _}) do
    order_by(query, [u], fragment("? asc nulls last", u.id))
  end

  defp restrict(query, :order, _options) do
    order_by(query, [u], fragment("? desc nulls last", u.id))
  end

  defp restrict(query, :offset, %{offset: offset}) do
    offset(query, ^offset)
  end

  defp restrict(query, :limit, options) do
    limit = Map.get(options, :limit, @default_limit)

    query
    |> limit(^limit)
  end

  defp restrict(query, _, _), do: query

  defp enforce_order(result, %{min_id: _}) do
    result
    |> Enum.reverse()
  end

  defp enforce_order(result, _), do: result
end