mirror of
https://github.com/philomena-dev/philomena.git
synced 2025-01-19 22:27:59 +01:00
API cursor navigation
This commit is contained in:
parent
b45130f073
commit
1e3b3430fc
23 changed files with 292 additions and 85 deletions
146
lib/philomena_query/cursor.ex
Normal file
146
lib/philomena_query/cursor.ex
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
defmodule PhilomenaQuery.Cursor do
|
||||||
|
alias PhilomenaQuery.Search
|
||||||
|
alias Philomena.Repo
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
@typedoc """
|
||||||
|
The underlying cursor type, which contains the ordered sort field values
|
||||||
|
of a document.
|
||||||
|
"""
|
||||||
|
@type cursor :: [integer() | binary() | boolean()]
|
||||||
|
|
||||||
|
@typedoc """
|
||||||
|
A mapping of document IDs to cursors.
|
||||||
|
"""
|
||||||
|
@type cursor_map :: %{integer() => cursor()}
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Execute search with optional input cursor, and return results as tuple of
|
||||||
|
`{results, cursors}`.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
iex> search_records(
|
||||||
|
...> %{query: ..., sort: [%{created_at: :desc}, %{id: :desc}]}
|
||||||
|
...> Image
|
||||||
|
...> )
|
||||||
|
{%Scrivener.Page{entries: [%Image{id: 1}, ...]},
|
||||||
|
%{1 => [1325394000000, 1], ...}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
@spec search_records(Search.search_definition(), Search.queryable(), search_after :: term()) ::
|
||||||
|
{Scrivener.Page.t(), cursor_map()}
|
||||||
|
def search_records(search_definition, queryable, search_after) do
|
||||||
|
search_definition = search_after_definition(search_definition, search_after)
|
||||||
|
page = Search.search_records_with_hits(search_definition, queryable)
|
||||||
|
|
||||||
|
{records, cursors} =
|
||||||
|
Enum.map_reduce(page, %{}, fn {record, hit}, cursors ->
|
||||||
|
sort = Map.fetch!(hit, "sort")
|
||||||
|
|
||||||
|
{record, Map.put(cursors, record.id, sort)}
|
||||||
|
end)
|
||||||
|
|
||||||
|
{Map.put(page, :entries, records), cursors}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Return page of records and cursors map based on sort.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
iex> paginate(Forum, [page_size: 25], ["dis", 3], asc: :name, asc: :id)
|
||||||
|
%{4 => ["Generals", 4]}
|
||||||
|
|
||||||
|
"""
|
||||||
|
@spec paginate(
|
||||||
|
Ecto.Query.t(),
|
||||||
|
scrivener_opts :: any(),
|
||||||
|
search_after :: term(),
|
||||||
|
sorts :: Keyword.t()
|
||||||
|
) :: {Scrivener.Page.t(), cursor_map()}
|
||||||
|
def paginate(query, pagination, search_after, sorts) do
|
||||||
|
total_entries = Repo.aggregate(query, :count)
|
||||||
|
pagination = Keyword.merge(pagination, options: [total_entries: total_entries])
|
||||||
|
|
||||||
|
records =
|
||||||
|
query
|
||||||
|
|> order_by(^sorts)
|
||||||
|
|> search_after_query(search_after, sorts)
|
||||||
|
|> Repo.paginate(pagination)
|
||||||
|
|
||||||
|
fields = Keyword.values(sorts)
|
||||||
|
|
||||||
|
cursors =
|
||||||
|
Enum.reduce(records, %{}, fn record, cursors ->
|
||||||
|
field_values = Enum.map(fields, &Map.fetch!(record, &1))
|
||||||
|
Map.put(cursors, record.id, field_values)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{records, cursors}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec search_after_definition(Search.search_definition(), term()) :: Search.search_definition()
|
||||||
|
defp search_after_definition(search_definition, search_after) do
|
||||||
|
search_after
|
||||||
|
|> permit_search_after()
|
||||||
|
|> case do
|
||||||
|
[] ->
|
||||||
|
search_definition
|
||||||
|
|
||||||
|
search_after ->
|
||||||
|
update_in(search_definition.body, &Map.put(&1, :search_after, search_after))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec search_after_query(Ecto.Query.t(), term(), Keyword.t()) :: Ecto.Query.t()
|
||||||
|
defp search_after_query(query, search_after, sorts) do
|
||||||
|
search_after = permit_search_after(search_after)
|
||||||
|
combined = Enum.zip(sorts, search_after)
|
||||||
|
|
||||||
|
case combined do
|
||||||
|
[_some | _rest] = values ->
|
||||||
|
or_clauses = dynamic([], false)
|
||||||
|
|
||||||
|
{or_clauses, _} =
|
||||||
|
Enum.reduce(values, {or_clauses, []}, fn {{sd, col}, value}, {next, equal_parts} ->
|
||||||
|
# more specific column has next value
|
||||||
|
and_clauses =
|
||||||
|
if sd == :asc do
|
||||||
|
dynamic([s], field(s, ^col) > ^value)
|
||||||
|
else
|
||||||
|
dynamic([s], field(s, ^col) < ^value)
|
||||||
|
end
|
||||||
|
|
||||||
|
# and
|
||||||
|
and_clauses =
|
||||||
|
Enum.reduce(equal_parts, and_clauses, fn {col, value}, rest ->
|
||||||
|
# less specific columns are equal
|
||||||
|
dynamic([s], field(s, ^col) == ^value and ^rest)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{dynamic(^next or ^and_clauses), equal_parts ++ [{col, value}]}
|
||||||
|
end)
|
||||||
|
|
||||||
|
where(query, ^or_clauses)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
query
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Validate that search_after values are only strings, numbers, and bools
|
||||||
|
defp permit_search_after(search_after) do
|
||||||
|
search_after
|
||||||
|
|> permit_list()
|
||||||
|
|> Enum.flat_map(&permit_value/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp permit_list(value) when is_list(value), do: value
|
||||||
|
defp permit_list(_value), do: []
|
||||||
|
|
||||||
|
defp permit_value(value) when is_binary(value) or is_number(value) or is_boolean(value),
|
||||||
|
do: [value]
|
||||||
|
|
||||||
|
defp permit_value(_value), do: []
|
||||||
|
end
|
|
@ -2,18 +2,21 @@ defmodule PhilomenaWeb.Api.Json.Filter.SystemFilterController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
alias Philomena.Filters.Filter
|
alias Philomena.Filters.Filter
|
||||||
alias Philomena.Repo
|
alias PhilomenaQuery.Cursor
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
def index(conn, _params) do
|
def index(conn, params) do
|
||||||
system_filters =
|
{system_filters, cursors} =
|
||||||
Filter
|
Filter
|
||||||
|> where(system: true)
|
|> where(system: true)
|
||||||
|> order_by(asc: :id)
|
|> Cursor.paginate(conn.assigns.scrivener, params["search_after"], asc: :id)
|
||||||
|> Repo.paginate(conn.assigns.scrivener)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(PhilomenaWeb.Api.Json.FilterView)
|
|> put_view(PhilomenaWeb.Api.Json.FilterView)
|
||||||
|> render("index.json", filters: system_filters, total: system_filters.total_entries)
|
|> render("index.json",
|
||||||
|
cursors: cursors,
|
||||||
|
filters: system_filters,
|
||||||
|
total: system_filters.total_entries
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,10 +2,10 @@ defmodule PhilomenaWeb.Api.Json.Filter.UserFilterController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
alias Philomena.Filters.Filter
|
alias Philomena.Filters.Filter
|
||||||
alias Philomena.Repo
|
alias PhilomenaQuery.Cursor
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
def index(conn, _params) do
|
def index(conn, params) do
|
||||||
user = conn.assigns.current_user
|
user = conn.assigns.current_user
|
||||||
|
|
||||||
case user do
|
case user do
|
||||||
|
@ -15,15 +15,18 @@ defmodule PhilomenaWeb.Api.Json.Filter.UserFilterController do
|
||||||
|> text("")
|
|> text("")
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
user_filters =
|
{user_filters, cursors} =
|
||||||
Filter
|
Filter
|
||||||
|> where(user_id: ^user.id)
|
|> where(user_id: ^user.id)
|
||||||
|> order_by(asc: :id)
|
|> Cursor.paginate(conn.assigns.scrivener, params["search_after"], asc: :id)
|
||||||
|> Repo.paginate(conn.assigns.scrivener)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(PhilomenaWeb.Api.Json.FilterView)
|
|> put_view(PhilomenaWeb.Api.Json.FilterView)
|
||||||
|> render("index.json", filters: user_filters, total: user_filters.total_entries)
|
|> render("index.json",
|
||||||
|
cursors: cursors,
|
||||||
|
filters: user_filters,
|
||||||
|
total: user_filters.total_entries
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,39 +1,46 @@
|
||||||
defmodule PhilomenaWeb.Api.Json.Forum.Topic.PostController do
|
defmodule PhilomenaWeb.Api.Json.Forum.Topic.PostController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias Philomena.Topics.Topic
|
||||||
alias Philomena.Posts.Post
|
alias Philomena.Posts.Post
|
||||||
|
alias PhilomenaQuery.Cursor
|
||||||
alias Philomena.Repo
|
alias Philomena.Repo
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
def index(conn, %{
|
||||||
|
"forum_id" => forum_id,
|
||||||
|
"topic_id" => topic_id,
|
||||||
|
"search_after" => search_after
|
||||||
|
}) do
|
||||||
|
topic = Repo.one!(topic_query(topic_id, forum_id))
|
||||||
|
|
||||||
|
{posts, cursors} =
|
||||||
|
post_query(topic_id, forum_id)
|
||||||
|
|> Cursor.paginate(conn.assigns.scrivener, search_after, asc: :topic_position)
|
||||||
|
|
||||||
|
render(conn, "index.json", cursors: cursors, posts: posts, total: topic.post_count)
|
||||||
|
end
|
||||||
|
|
||||||
def index(conn, %{"forum_id" => forum_id, "topic_id" => topic_id}) do
|
def index(conn, %{"forum_id" => forum_id, "topic_id" => topic_id}) do
|
||||||
page = conn.assigns.pagination.page_number
|
page = conn.assigns.pagination.page_number
|
||||||
|
|
||||||
posts =
|
topic = Repo.one!(topic_query(topic_id, forum_id))
|
||||||
Post
|
|
||||||
|> join(:inner, [p], _ in assoc(p, :topic))
|
|
||||||
|> join(:inner, [_p, t], _ in assoc(t, :forum))
|
|
||||||
|> where(destroyed_content: false)
|
|
||||||
|> where([_p, t], t.hidden_from_users == false and t.slug == ^topic_id)
|
|
||||||
|> where([_p, _t, f], f.access_level == "normal" and f.short_name == ^forum_id)
|
|
||||||
|> where([p], p.topic_position >= ^(25 * (page - 1)) and p.topic_position < ^(25 * page))
|
|
||||||
|> order_by(asc: :topic_position)
|
|
||||||
|> preload([:user, :topic])
|
|
||||||
|> preload([_p, t, _f], topic: t)
|
|
||||||
|> Repo.all()
|
|
||||||
|
|
||||||
render(conn, "index.json", posts: posts, total: hd(posts).topic.post_count)
|
{posts, cursors} =
|
||||||
|
post_query(topic_id, forum_id)
|
||||||
|
|> where(
|
||||||
|
[posts: p],
|
||||||
|
p.topic_position >= ^(25 * (page - 1)) and p.topic_position < ^(25 * page)
|
||||||
|
)
|
||||||
|
|> Cursor.paginate([page_size: 25], [], asc: :topic_position)
|
||||||
|
|
||||||
|
render(conn, "index.json", cursors: cursors, posts: posts, total: topic.post_count)
|
||||||
end
|
end
|
||||||
|
|
||||||
def show(conn, %{"forum_id" => forum_id, "topic_id" => topic_id, "id" => post_id}) do
|
def show(conn, %{"forum_id" => forum_id, "topic_id" => topic_id, "id" => post_id}) do
|
||||||
post =
|
post =
|
||||||
Post
|
post_query(forum_id, topic_id)
|
||||||
|> join(:inner, [p], _ in assoc(p, :topic))
|
|
||||||
|> join(:inner, [_p, t], _ in assoc(t, :forum))
|
|
||||||
|> where(id: ^post_id)
|
|> where(id: ^post_id)
|
||||||
|> where(destroyed_content: false)
|
|
||||||
|> where([_p, t], t.hidden_from_users == false and t.slug == ^topic_id)
|
|
||||||
|> where([_p, _t, f], f.access_level == "normal" and f.short_name == ^forum_id)
|
|
||||||
|> preload([:user, :topic])
|
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
|
@ -46,4 +53,27 @@ defmodule PhilomenaWeb.Api.Json.Forum.Topic.PostController do
|
||||||
render(conn, "show.json", post: post)
|
render(conn, "show.json", post: post)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp topic_query(topic_id, forum_id) do
|
||||||
|
Topic
|
||||||
|
|> from(as: :topic)
|
||||||
|
|> join(:inner, [topic: t], _ in assoc(t, :forum), as: :forum)
|
||||||
|
|> topic_conditions(topic_id, forum_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp post_query(topic_id, forum_id) do
|
||||||
|
Post
|
||||||
|
|> from(as: :posts)
|
||||||
|
|> join(:inner, [posts: p], _ in assoc(p, :topic), as: :topic)
|
||||||
|
|> join(:inner, [topic: t], _ in assoc(t, :forum), as: :forum)
|
||||||
|
|> topic_conditions(topic_id, forum_id)
|
||||||
|
|> where([posts: p], p.destroyed_content == false)
|
||||||
|
|> preload([:user])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp topic_conditions(queryable, topic_id, forum_id) do
|
||||||
|
queryable
|
||||||
|
|> where([topic: t], t.hidden_from_users == false and t.slug == ^topic_id)
|
||||||
|
|> where([forum: f], f.access_level == "normal" and f.short_name == ^forum_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,20 +2,24 @@ defmodule PhilomenaWeb.Api.Json.Forum.TopicController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
alias Philomena.Topics.Topic
|
alias Philomena.Topics.Topic
|
||||||
|
alias PhilomenaQuery.Cursor
|
||||||
alias Philomena.Repo
|
alias Philomena.Repo
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
def index(conn, %{"forum_id" => id}) do
|
def index(conn, %{"forum_id" => id} = params) do
|
||||||
topics =
|
{topics, cursors} =
|
||||||
Topic
|
Topic
|
||||||
|> join(:inner, [t], _ in assoc(t, :forum))
|
|> join(:inner, [t], _ in assoc(t, :forum))
|
||||||
|> where(hidden_from_users: false)
|
|> where(hidden_from_users: false)
|
||||||
|> where([_t, f], f.access_level == "normal" and f.short_name == ^id)
|
|> where([_t, f], f.access_level == "normal" and f.short_name == ^id)
|
||||||
|> order_by(desc: :sticky, desc: :last_replied_to_at)
|
|
||||||
|> preload([:user])
|
|> preload([:user])
|
||||||
|> Repo.paginate(conn.assigns.scrivener)
|
|> Cursor.paginate(conn.assigns.scrivener, params["search_after"],
|
||||||
|
desc: :sticky,
|
||||||
|
desc: :last_replied_to_at,
|
||||||
|
desc: :slug
|
||||||
|
)
|
||||||
|
|
||||||
render(conn, "index.json", topics: topics, total: topics.total_entries)
|
render(conn, "index.json", cursors: cursors, topics: topics, total: topics.total_entries)
|
||||||
end
|
end
|
||||||
|
|
||||||
def show(conn, %{"forum_id" => forum_id, "id" => id}) do
|
def show(conn, %{"forum_id" => forum_id, "id" => id}) do
|
||||||
|
@ -25,7 +29,6 @@ defmodule PhilomenaWeb.Api.Json.Forum.TopicController do
|
||||||
|> where(slug: ^id)
|
|> where(slug: ^id)
|
||||||
|> where(hidden_from_users: false)
|
|> where(hidden_from_users: false)
|
||||||
|> where([_t, f], f.access_level == "normal" and f.short_name == ^forum_id)
|
|> where([_t, f], f.access_level == "normal" and f.short_name == ^forum_id)
|
||||||
|> order_by(desc: :sticky, desc: :last_replied_to_at)
|
|
||||||
|> preload([:user])
|
|> preload([:user])
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
defmodule PhilomenaWeb.Api.Json.ForumController do
|
defmodule PhilomenaWeb.Api.Json.ForumController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias PhilomenaQuery.Cursor
|
||||||
alias Philomena.Forums.Forum
|
alias Philomena.Forums.Forum
|
||||||
alias Philomena.Repo
|
alias Philomena.Repo
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
def index(conn, _params) do
|
def index(conn, params) do
|
||||||
forums =
|
{forums, cursors} =
|
||||||
Forum
|
Forum
|
||||||
|> where(access_level: "normal")
|
|> where(access_level: "normal")
|
||||||
|> order_by(asc: :name)
|
|> Cursor.paginate(conn.assigns.scrivener, params["search_after"],
|
||||||
|> Repo.paginate(conn.assigns.scrivener)
|
asc: :name,
|
||||||
|
asc: :short_name
|
||||||
|
)
|
||||||
|
|
||||||
render(conn, forums: forums, total: forums.total_entries)
|
render(conn, cursors: cursors, forums: forums, total: forums.total_entries)
|
||||||
end
|
end
|
||||||
|
|
||||||
def show(conn, %{"id" => id}) do
|
def show(conn, %{"id" => id}) do
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule PhilomenaWeb.Api.Json.Search.CommentController do
|
defmodule PhilomenaWeb.Api.Json.Search.CommentController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias PhilomenaQuery.Cursor
|
||||||
alias PhilomenaQuery.Search
|
alias PhilomenaQuery.Search
|
||||||
alias Philomena.Comments.Comment
|
alias Philomena.Comments.Comment
|
||||||
alias Philomena.Comments.Query
|
alias Philomena.Comments.Query
|
||||||
|
@ -12,7 +13,7 @@ defmodule PhilomenaWeb.Api.Json.Search.CommentController do
|
||||||
|
|
||||||
case Query.compile(params["q"], user: user) do
|
case Query.compile(params["q"], user: user) do
|
||||||
{:ok, query} ->
|
{:ok, query} ->
|
||||||
comments =
|
{comments, cursors} =
|
||||||
Comment
|
Comment
|
||||||
|> Search.search_definition(
|
|> Search.search_definition(
|
||||||
%{
|
%{
|
||||||
|
@ -27,15 +28,19 @@ defmodule PhilomenaWeb.Api.Json.Search.CommentController do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sort: %{posted_at: :desc}
|
sort: [%{posted_at: :desc}, %{id: :desc}]
|
||||||
},
|
},
|
||||||
conn.assigns.pagination
|
conn.assigns.pagination
|
||||||
)
|
)
|
||||||
|> Search.search_records(preload(Comment, [:image, :user]))
|
|> Cursor.search_records(preload(Comment, [:image, :user]), params["search_after"])
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(PhilomenaWeb.Api.Json.CommentView)
|
|> put_view(PhilomenaWeb.Api.Json.CommentView)
|
||||||
|> render("index.json", comments: comments, total: comments.total_entries)
|
|> render("index.json",
|
||||||
|
comments: comments,
|
||||||
|
cursors: cursors,
|
||||||
|
total: comments.total_entries
|
||||||
|
)
|
||||||
|
|
||||||
{:error, msg} ->
|
{:error, msg} ->
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule PhilomenaWeb.Api.Json.Search.FilterController do
|
defmodule PhilomenaWeb.Api.Json.Search.FilterController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias PhilomenaQuery.Cursor
|
||||||
alias PhilomenaQuery.Search
|
alias PhilomenaQuery.Search
|
||||||
alias Philomena.Filters.Filter
|
alias Philomena.Filters.Filter
|
||||||
alias Philomena.Filters.Query
|
alias Philomena.Filters.Query
|
||||||
|
@ -11,7 +12,7 @@ defmodule PhilomenaWeb.Api.Json.Search.FilterController do
|
||||||
|
|
||||||
case Query.compile(params["q"], user: user) do
|
case Query.compile(params["q"], user: user) do
|
||||||
{:ok, query} ->
|
{:ok, query} ->
|
||||||
filters =
|
{filters, cursors} =
|
||||||
Filter
|
Filter
|
||||||
|> Search.search_definition(
|
|> Search.search_definition(
|
||||||
%{
|
%{
|
||||||
|
@ -36,11 +37,11 @@ defmodule PhilomenaWeb.Api.Json.Search.FilterController do
|
||||||
},
|
},
|
||||||
conn.assigns.pagination
|
conn.assigns.pagination
|
||||||
)
|
)
|
||||||
|> Search.search_records(preload(Filter, [:user]))
|
|> Cursor.search_records(preload(Filter, [:user]), params["search_after"])
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(PhilomenaWeb.Api.Json.FilterView)
|
|> put_view(PhilomenaWeb.Api.Json.FilterView)
|
||||||
|> render("index.json", filters: filters, total: filters.total_entries)
|
|> render("index.json", cursors: cursors, filters: filters, total: filters.total_entries)
|
||||||
|
|
||||||
{:error, msg} ->
|
{:error, msg} ->
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule PhilomenaWeb.Api.Json.Search.GalleryController do
|
defmodule PhilomenaWeb.Api.Json.Search.GalleryController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias PhilomenaQuery.Cursor
|
||||||
alias PhilomenaQuery.Search
|
alias PhilomenaQuery.Search
|
||||||
alias Philomena.Galleries.Gallery
|
alias Philomena.Galleries.Gallery
|
||||||
alias Philomena.Galleries.Query
|
alias Philomena.Galleries.Query
|
||||||
|
@ -9,20 +10,24 @@ defmodule PhilomenaWeb.Api.Json.Search.GalleryController do
|
||||||
def index(conn, params) do
|
def index(conn, params) do
|
||||||
case Query.compile(params["q"]) do
|
case Query.compile(params["q"]) do
|
||||||
{:ok, query} ->
|
{:ok, query} ->
|
||||||
galleries =
|
{galleries, cursors} =
|
||||||
Gallery
|
Gallery
|
||||||
|> Search.search_definition(
|
|> Search.search_definition(
|
||||||
%{
|
%{
|
||||||
query: query,
|
query: query,
|
||||||
sort: %{created_at: :desc}
|
sort: [%{created_at: :desc}, %{id: :desc}]
|
||||||
},
|
},
|
||||||
conn.assigns.pagination
|
conn.assigns.pagination
|
||||||
)
|
)
|
||||||
|> Search.search_records(preload(Gallery, [:creator]))
|
|> Cursor.search_records(preload(Gallery, [:creator]), params["search_after"])
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(PhilomenaWeb.Api.Json.GalleryView)
|
|> put_view(PhilomenaWeb.Api.Json.GalleryView)
|
||||||
|> render("index.json", galleries: galleries, total: galleries.total_entries)
|
|> render("index.json",
|
||||||
|
cursors: cursors,
|
||||||
|
galleries: galleries,
|
||||||
|
total: galleries.total_entries
|
||||||
|
)
|
||||||
|
|
||||||
{:error, msg} ->
|
{:error, msg} ->
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -2,7 +2,7 @@ defmodule PhilomenaWeb.Api.Json.Search.ImageController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
alias PhilomenaWeb.ImageLoader
|
alias PhilomenaWeb.ImageLoader
|
||||||
alias PhilomenaQuery.Search
|
alias PhilomenaQuery.Cursor
|
||||||
alias Philomena.Interactions
|
alias Philomena.Interactions
|
||||||
alias Philomena.Images.Image
|
alias Philomena.Images.Image
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
@ -13,13 +13,14 @@ defmodule PhilomenaWeb.Api.Json.Search.ImageController do
|
||||||
|
|
||||||
case ImageLoader.search_string(conn, params["q"]) do
|
case ImageLoader.search_string(conn, params["q"]) do
|
||||||
{:ok, {images, _tags}} ->
|
{:ok, {images, _tags}} ->
|
||||||
images = Search.search_records(images, queryable)
|
{images, cursors} = Cursor.search_records(images, queryable, params["search_after"])
|
||||||
interactions = Interactions.user_interactions(images, user)
|
interactions = Interactions.user_interactions(images, user)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(PhilomenaWeb.Api.Json.ImageView)
|
|> put_view(PhilomenaWeb.Api.Json.ImageView)
|
||||||
|> render("index.json",
|
|> render("index.json",
|
||||||
images: images,
|
images: images,
|
||||||
|
cursors: cursors,
|
||||||
total: images.total_entries,
|
total: images.total_entries,
|
||||||
interactions: interactions
|
interactions: interactions
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule PhilomenaWeb.Api.Json.Search.PostController do
|
defmodule PhilomenaWeb.Api.Json.Search.PostController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias PhilomenaQuery.Cursor
|
||||||
alias PhilomenaQuery.Search
|
alias PhilomenaQuery.Search
|
||||||
alias Philomena.Posts.Post
|
alias Philomena.Posts.Post
|
||||||
alias Philomena.Posts.Query
|
alias Philomena.Posts.Query
|
||||||
|
@ -11,7 +12,7 @@ defmodule PhilomenaWeb.Api.Json.Search.PostController do
|
||||||
|
|
||||||
case Query.compile(params["q"], user: user) do
|
case Query.compile(params["q"], user: user) do
|
||||||
{:ok, query} ->
|
{:ok, query} ->
|
||||||
posts =
|
{posts, cursors} =
|
||||||
Post
|
Post
|
||||||
|> Search.search_definition(
|
|> Search.search_definition(
|
||||||
%{
|
%{
|
||||||
|
@ -24,15 +25,15 @@ defmodule PhilomenaWeb.Api.Json.Search.PostController do
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sort: %{created_at: :desc}
|
sort: [%{created_at: :desc}, %{id: :desc}]
|
||||||
},
|
},
|
||||||
conn.assigns.pagination
|
conn.assigns.pagination
|
||||||
)
|
)
|
||||||
|> Search.search_records(preload(Post, [:user, :topic]))
|
|> Cursor.search_records(preload(Post, [:user, :topic]), params["search_after"])
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(PhilomenaWeb.Api.Json.Forum.Topic.PostView)
|
|> put_view(PhilomenaWeb.Api.Json.Forum.Topic.PostView)
|
||||||
|> render("index.json", posts: posts, total: posts.total_entries)
|
|> render("index.json", cursors: cursors, posts: posts, total: posts.total_entries)
|
||||||
|
|
||||||
{:error, msg} ->
|
{:error, msg} ->
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -29,6 +29,7 @@ defmodule PhilomenaWeb.Api.Json.Search.ReverseController do
|
||||||
|> put_view(PhilomenaWeb.Api.Json.ImageView)
|
|> put_view(PhilomenaWeb.Api.Json.ImageView)
|
||||||
|> render("index.json",
|
|> render("index.json",
|
||||||
images: images,
|
images: images,
|
||||||
|
cursors: %{},
|
||||||
total: total,
|
total: total,
|
||||||
interactions: interactions
|
interactions: interactions
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule PhilomenaWeb.Api.Json.Search.TagController do
|
defmodule PhilomenaWeb.Api.Json.Search.TagController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias PhilomenaQuery.Cursor
|
||||||
alias PhilomenaQuery.Search
|
alias PhilomenaQuery.Search
|
||||||
alias Philomena.Tags.Tag
|
alias Philomena.Tags.Tag
|
||||||
alias Philomena.Tags.Query
|
alias Philomena.Tags.Query
|
||||||
|
@ -9,19 +10,20 @@ defmodule PhilomenaWeb.Api.Json.Search.TagController do
|
||||||
def index(conn, params) do
|
def index(conn, params) do
|
||||||
case Query.compile(params["q"]) do
|
case Query.compile(params["q"]) do
|
||||||
{:ok, query} ->
|
{:ok, query} ->
|
||||||
tags =
|
{tags, cursors} =
|
||||||
Tag
|
Tag
|
||||||
|> Search.search_definition(
|
|> Search.search_definition(
|
||||||
%{query: query, sort: %{images: :desc}},
|
%{query: query, sort: [%{images: :desc}, %{id: :desc}]},
|
||||||
conn.assigns.pagination
|
conn.assigns.pagination
|
||||||
)
|
)
|
||||||
|> Search.search_records(
|
|> Cursor.search_records(
|
||||||
preload(Tag, [:aliased_tag, :aliases, :implied_tags, :implied_by_tags, :dnp_entries])
|
preload(Tag, [:aliased_tag, :aliases, :implied_tags, :implied_by_tags, :dnp_entries]),
|
||||||
|
params["search_after"]
|
||||||
)
|
)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(PhilomenaWeb.Api.Json.TagView)
|
|> put_view(PhilomenaWeb.Api.Json.TagView)
|
||||||
|> render("index.json", tags: tags, total: tags.total_entries)
|
|> render("index.json", cursors: cursors, tags: tags, total: tags.total_entries)
|
||||||
|
|
||||||
{:error, msg} ->
|
{:error, msg} ->
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -80,7 +80,7 @@ defmodule PhilomenaWeb.ImageSorter do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_sf(_params, sd, query) do
|
defp parse_sf(_params, sd, query) do
|
||||||
%{query: query, sorts: [%{"first_seen_at" => sd}]}
|
%{query: query, sorts: [%{"first_seen_at" => sd}, %{"id" => sd}]}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp random_query(seed, sd, query) do
|
defp random_query(seed, sd, query) do
|
||||||
|
|
|
@ -2,8 +2,9 @@ defmodule PhilomenaWeb.Api.Json.CommentView do
|
||||||
use PhilomenaWeb, :view
|
use PhilomenaWeb, :view
|
||||||
alias PhilomenaWeb.UserAttributionView
|
alias PhilomenaWeb.UserAttributionView
|
||||||
|
|
||||||
def render("index.json", %{comments: comments, total: total} = assigns) do
|
def render("index.json", %{cursors: cursors, comments: comments, total: total} = assigns) do
|
||||||
%{
|
%{
|
||||||
|
cursors: cursors,
|
||||||
comments: render_many(comments, PhilomenaWeb.Api.Json.CommentView, "comment.json", assigns),
|
comments: render_many(comments, PhilomenaWeb.Api.Json.CommentView, "comment.json", assigns),
|
||||||
total: total
|
total: total
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,6 @@
|
||||||
defmodule PhilomenaWeb.Api.Json.DnpView do
|
defmodule PhilomenaWeb.Api.Json.DnpView do
|
||||||
use PhilomenaWeb, :view
|
use PhilomenaWeb, :view
|
||||||
|
|
||||||
def render("index.json", %{dnps: dnp, total: total} = assigns) do
|
|
||||||
%{
|
|
||||||
dnps: render_many(dnp, PhilomenaWeb.Api.Json.DnpView, "dnp.json", assigns),
|
|
||||||
total: total
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def render("show.json", %{dnp: dnp} = assigns) do
|
|
||||||
%{dnp: render_one(dnp, PhilomenaWeb.Api.Json.DnpView, "dnp.json", assigns)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def render("dnp.json", %{dnp: dnp}) do
|
def render("dnp.json", %{dnp: dnp}) do
|
||||||
%{
|
%{
|
||||||
id: dnp.id,
|
id: dnp.id,
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
defmodule PhilomenaWeb.Api.Json.FilterView do
|
defmodule PhilomenaWeb.Api.Json.FilterView do
|
||||||
use PhilomenaWeb, :view
|
use PhilomenaWeb, :view
|
||||||
|
|
||||||
def render("index.json", %{filters: filters, total: total} = assigns) do
|
def render("index.json", %{cursors: cursors, filters: filters, total: total} = assigns) do
|
||||||
%{
|
%{
|
||||||
|
cursors: cursors,
|
||||||
filters: render_many(filters, PhilomenaWeb.Api.Json.FilterView, "filter.json", assigns),
|
filters: render_many(filters, PhilomenaWeb.Api.Json.FilterView, "filter.json", assigns),
|
||||||
total: total
|
total: total
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,9 @@ defmodule PhilomenaWeb.Api.Json.Forum.Topic.PostView do
|
||||||
use PhilomenaWeb, :view
|
use PhilomenaWeb, :view
|
||||||
alias PhilomenaWeb.UserAttributionView
|
alias PhilomenaWeb.UserAttributionView
|
||||||
|
|
||||||
def render("index.json", %{posts: posts, total: total} = assigns) do
|
def render("index.json", %{cursors: cursors, posts: posts, total: total} = assigns) do
|
||||||
%{
|
%{
|
||||||
|
cursors: cursors,
|
||||||
posts: render_many(posts, PhilomenaWeb.Api.Json.Forum.Topic.PostView, "post.json", assigns),
|
posts: render_many(posts, PhilomenaWeb.Api.Json.Forum.Topic.PostView, "post.json", assigns),
|
||||||
total: total
|
total: total
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,9 @@ defmodule PhilomenaWeb.Api.Json.Forum.TopicView do
|
||||||
use PhilomenaWeb, :view
|
use PhilomenaWeb, :view
|
||||||
alias PhilomenaWeb.UserAttributionView
|
alias PhilomenaWeb.UserAttributionView
|
||||||
|
|
||||||
def render("index.json", %{topics: topics, total: total} = assigns) do
|
def render("index.json", %{cursors: cursors, topics: topics, total: total} = assigns) do
|
||||||
%{
|
%{
|
||||||
|
cursors: cursors,
|
||||||
topics: render_many(topics, PhilomenaWeb.Api.Json.Forum.TopicView, "topic.json", assigns),
|
topics: render_many(topics, PhilomenaWeb.Api.Json.Forum.TopicView, "topic.json", assigns),
|
||||||
total: total
|
total: total
|
||||||
}
|
}
|
||||||
|
@ -15,6 +16,7 @@ defmodule PhilomenaWeb.Api.Json.Forum.TopicView do
|
||||||
|
|
||||||
def render("topic.json", %{topic: %{hidden_from_users: true}}) do
|
def render("topic.json", %{topic: %{hidden_from_users: true}}) do
|
||||||
%{
|
%{
|
||||||
|
id: nil,
|
||||||
slug: nil,
|
slug: nil,
|
||||||
title: nil,
|
title: nil,
|
||||||
post_count: nil,
|
post_count: nil,
|
||||||
|
@ -29,6 +31,7 @@ defmodule PhilomenaWeb.Api.Json.Forum.TopicView do
|
||||||
|
|
||||||
def render("topic.json", %{topic: topic}) do
|
def render("topic.json", %{topic: topic}) do
|
||||||
%{
|
%{
|
||||||
|
id: topic.id,
|
||||||
slug: topic.slug,
|
slug: topic.slug,
|
||||||
title: topic.title,
|
title: topic.title,
|
||||||
post_count: topic.post_count,
|
post_count: topic.post_count,
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
defmodule PhilomenaWeb.Api.Json.ForumView do
|
defmodule PhilomenaWeb.Api.Json.ForumView do
|
||||||
use PhilomenaWeb, :view
|
use PhilomenaWeb, :view
|
||||||
|
|
||||||
def render("index.json", %{forums: forums, total: total} = assigns) do
|
def render("index.json", %{cursors: cursors, forums: forums, total: total} = assigns) do
|
||||||
%{
|
%{
|
||||||
|
cursors: cursors,
|
||||||
forums: render_many(forums, PhilomenaWeb.Api.Json.ForumView, "forum.json", assigns),
|
forums: render_many(forums, PhilomenaWeb.Api.Json.ForumView, "forum.json", assigns),
|
||||||
total: total
|
total: total
|
||||||
}
|
}
|
||||||
|
@ -14,6 +15,7 @@ defmodule PhilomenaWeb.Api.Json.ForumView do
|
||||||
|
|
||||||
def render("forum.json", %{forum: %{access_level: "normal"} = forum}) do
|
def render("forum.json", %{forum: %{access_level: "normal"} = forum}) do
|
||||||
%{
|
%{
|
||||||
|
id: forum.id,
|
||||||
name: forum.name,
|
name: forum.name,
|
||||||
short_name: forum.short_name,
|
short_name: forum.short_name,
|
||||||
description: forum.description,
|
description: forum.description,
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
defmodule PhilomenaWeb.Api.Json.GalleryView do
|
defmodule PhilomenaWeb.Api.Json.GalleryView do
|
||||||
use PhilomenaWeb, :view
|
use PhilomenaWeb, :view
|
||||||
|
|
||||||
def render("index.json", %{galleries: galleries, total: total} = assigns) do
|
def render("index.json", %{cursors: cursors, galleries: galleries, total: total} = assigns) do
|
||||||
%{
|
%{
|
||||||
|
cursors: cursors,
|
||||||
galleries:
|
galleries:
|
||||||
render_many(galleries, PhilomenaWeb.Api.Json.GalleryView, "gallery.json", assigns),
|
render_many(galleries, PhilomenaWeb.Api.Json.GalleryView, "gallery.json", assigns),
|
||||||
total: total
|
total: total
|
||||||
|
|
|
@ -2,8 +2,12 @@ defmodule PhilomenaWeb.Api.Json.ImageView do
|
||||||
use PhilomenaWeb, :view
|
use PhilomenaWeb, :view
|
||||||
alias PhilomenaWeb.ImageView
|
alias PhilomenaWeb.ImageView
|
||||||
|
|
||||||
def render("index.json", %{images: images, interactions: interactions, total: total} = assigns) do
|
def render(
|
||||||
|
"index.json",
|
||||||
|
%{cursors: cursors, images: images, interactions: interactions, total: total} = assigns
|
||||||
|
) do
|
||||||
%{
|
%{
|
||||||
|
cursors: cursors,
|
||||||
images: render_many(images, PhilomenaWeb.Api.Json.ImageView, "image.json", assigns),
|
images: render_many(images, PhilomenaWeb.Api.Json.ImageView, "image.json", assigns),
|
||||||
interactions: interactions,
|
interactions: interactions,
|
||||||
total: total
|
total: total
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
defmodule PhilomenaWeb.Api.Json.TagView do
|
defmodule PhilomenaWeb.Api.Json.TagView do
|
||||||
use PhilomenaWeb, :view
|
use PhilomenaWeb, :view
|
||||||
|
|
||||||
def render("index.json", %{tags: tags, total: total} = assigns) do
|
def render("index.json", %{cursors: cursors, tags: tags, total: total} = assigns) do
|
||||||
%{
|
%{
|
||||||
|
cursors: cursors,
|
||||||
tags: render_many(tags, PhilomenaWeb.Api.Json.TagView, "tag.json", assigns),
|
tags: render_many(tags, PhilomenaWeb.Api.Json.TagView, "tag.json", assigns),
|
||||||
total: total
|
total: total
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue