Skip to content

Commit 57a5162

Browse files
authored
Merge pull request #36 from profiq/feature/page-size-and-offset
Add support for proper offsets and custom page sizes
2 parents 0193e8b + 33ed97b commit 57a5162

20 files changed

Lines changed: 491 additions & 121 deletions

config/config.exs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,7 @@ config :logger, :console,
5959
config :phoenix, :json_library, Jason
6060

6161
# Configure Flop
62-
config :flop,
63-
repo: SWAPI.Repo,
64-
pagination_types: [:page],
65-
default_limit: 10
62+
config :flop, repo: SWAPI.Repo
6663

6764
# Import environment specific config. This must remain at the bottom
6865
# of this file so it overrides the configuration defined above.

lib/swapi/films.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule SWAPI.Films do
77
alias SWAPI.Schemas.Film
88

99
import Ecto.Query, warn: false
10-
import SWAPI.Util
10+
import SWAPI.Pagination
1111

1212
def preload_all(film) do
1313
Repo.preload(film, [:species, :starships, :vehicles, :characters, :planets])

lib/swapi/pagination.ex

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
defmodule SWAPI.Pagination do
2+
@moduledoc """
3+
Utility functions
4+
"""
5+
6+
@type pagination_method :: :page | :offset
7+
@type page_info() :: %{
8+
count: non_neg_integer,
9+
next: {pagination_method, non_neg_integer} | nil,
10+
previous: {pagination_method, non_neg_integer} | nil
11+
}
12+
13+
@default_page_size 10
14+
15+
@doc """
16+
Queries the given Ecto queryable with pagination.
17+
"""
18+
@spec paginate(Ecto.Queryable.t(), map()) :: {:ok, {list, page_info}} | {:error, atom}
19+
def paginate(query, params) do
20+
with {:ok, params} <- check_pagination_methods(params) do
21+
do_paginate(query, params)
22+
end
23+
end
24+
25+
defp check_pagination_methods(params) do
26+
{methods, _} = Map.split(params, ~w(page offset))
27+
28+
if Enum.count(methods) <= 1 do
29+
{:ok, params}
30+
else
31+
{:error, :bad_request}
32+
end
33+
end
34+
35+
defp do_paginate(query, %{"page" => page} = params) when is_binary(page) do
36+
with {:ok, page} <- parse_integer(page),
37+
{:ok, page_size} <- parse_integer(params["limit"], @default_page_size),
38+
{list, meta} <- Flop.validate_and_run!(query, %Flop{page: page, page_size: page_size}) do
39+
{:ok,
40+
{list,
41+
%{
42+
count: meta.total_count,
43+
next: if(meta.next_page, do: {:page, meta.next_page}),
44+
previous: if(meta.previous_page, do: {:page, meta.previous_page})
45+
}}}
46+
end
47+
end
48+
49+
defp do_paginate(query, %{"offset" => offset} = params) when is_binary(offset) do
50+
with {:ok, offset} <- parse_integer(offset),
51+
{:ok, limit} <- parse_integer(params["limit"], @default_page_size),
52+
{list, meta} <- Flop.validate_and_run!(query, %Flop{offset: offset, limit: limit}) do
53+
{:ok,
54+
{list,
55+
%{
56+
count: meta.total_count,
57+
next: if(meta.next_offset, do: {:offset, meta.next_offset}),
58+
previous: if(meta.previous_offset, do: {:offset, meta.previous_offset})
59+
}}}
60+
end
61+
end
62+
63+
defp do_paginate(query, _), do: do_paginate(query, %{"page" => "1"})
64+
65+
defp parse_integer(value) do
66+
case Integer.parse(value) do
67+
:error -> {:error, :bad_request}
68+
{value, _} -> {:ok, value}
69+
end
70+
end
71+
72+
defp parse_integer(value, default_value) do
73+
if value do
74+
parse_integer(value)
75+
else
76+
{:ok, default_value}
77+
end
78+
end
79+
end

lib/swapi/people.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule SWAPI.People do
77
alias SWAPI.Schemas.Person
88

99
import Ecto.Query, warn: false
10-
import SWAPI.Util
10+
import SWAPI.Pagination
1111

1212
def preload_all(person) do
1313
Repo.preload(person, [:homeworld, :films, :species, :starships, :vehicles])

lib/swapi/planets.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule SWAPI.Planets do
77
alias SWAPI.Schemas.Planet
88

99
import Ecto.Query, warn: false
10-
import SWAPI.Util
10+
import SWAPI.Pagination
1111

1212
def preload_all(planet) do
1313
Repo.preload(planet, [:residents, :films])

lib/swapi/species.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule SWAPI.Species do
77
alias SWAPI.Schemas.Species
88

99
import Ecto.Query, warn: false
10-
import SWAPI.Util
10+
import SWAPI.Pagination
1111

1212
def preload_all(species) do
1313
Repo.preload(species, [:homeworld, :people, :films])

lib/swapi/starships.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ defmodule SWAPI.Starships do
88
alias SWAPI.Schemas.Transport
99

1010
import Ecto.Query, warn: false
11-
import SWAPI.Util
11+
import SWAPI.Pagination
1212

1313
def preload_all(starship) do
1414
Repo.preload(starship, [:transport, :films, :pilots])

lib/swapi/util.ex

Lines changed: 0 additions & 41 deletions
This file was deleted.

lib/swapi/vehicles.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ defmodule SWAPI.Vehicles do
88
alias SWAPI.Schemas.Vehicle
99

1010
import Ecto.Query, warn: false
11-
import SWAPI.Util
11+
import SWAPI.Pagination
1212

1313
def preload_all(vehicle) do
1414
Repo.preload(vehicle, [:transport, :films, :pilots])

lib/swapi_web/controllers/film_controller.ex

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ defmodule SWAPIWeb.FilmController do
22
use SWAPIWeb, :controller
33
use OpenApiSpex.ControllerSpecs
44

5+
alias OpenApiSpex.Schema
56
alias SWAPI.Films
67

78
import SWAPIWeb.Util
@@ -19,7 +20,32 @@ defmodule SWAPIWeb.FilmController do
1920
"One or more search terms, which should be whitespace and/or comma separated. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched. Searches may contain quoted phrases with spaces, each phrase is considered as a single search term.",
2021
type: :string
2122
],
22-
page: [in: :query, description: "Page number", type: :integer]
23+
page: [
24+
in: :query,
25+
description: "Page number. Cannot be used together with `offset`.",
26+
schema: %Schema{
27+
type: :integer,
28+
minimum: 1,
29+
default: 1
30+
}
31+
],
32+
offset: [
33+
in: :query,
34+
description: "Offset of the first item. Cannot be used together with `page`.",
35+
schema: %Schema{
36+
type: :integer,
37+
minimum: 0
38+
}
39+
],
40+
limit: [
41+
in: :query,
42+
description: "Maximum number of items to return in the response.",
43+
schema: %Schema{
44+
type: :integer,
45+
minimum: 1,
46+
default: 10
47+
}
48+
]
2349
],
2450
responses: [
2551
ok: {"List of films", "application/json", SWAPIWeb.Schemas.FilmList}

0 commit comments

Comments
 (0)