defmodule Literature do
@moduledoc false
use Cldr,
providers: [Cldr.Language]
import Literature.QueryHelpers
alias Literature.Author
alias Literature.DownloadHelpers
alias Literature.ImageComponent
alias Literature.Post
alias Literature.Publication
alias Literature.Redirect
alias Literature.Repo
alias Literature.Tag
alias Literature.TagPost
## Author Context
@doc """
Returns the paginate list of authors.
## Examples
iex> paginate_authors()
%Scrivener.Page{entries: [%Author{}, ...], ...}
"""
def paginate_authors(attrs \\ []) do
Author
|> search(:name, attrs)
|> search(:slug, attrs)
|> sort_by(attrs)
|> where_publication(attrs)
|> where_preload(%{"preload" => ~w(posts)a})
|> Repo.paginate(attrs)
end
@doc """
Returns the list of authors.
## Examples
iex> list_authors()
[%Author{}, ...]
"""
def list_authors(attrs \\ []) do
Author
|> sort_by(attrs)
|> where_preload(attrs)
|> where_publication(attrs)
|> Repo.all()
end
@doc """
Gets a single author.
Raises `Ecto.NoResultsError` if the Author does not exist.
## Examples
iex> get_author!(123)
%Author{}
iex> get_author!(456)
** (Ecto.NoResultsError)
"""
def get_author!(id) when is_binary(id), do: Repo.get(Author, id)
def get_author!(list) do
attrs = Keyword.delete(list, :publication_slug)
Author
|> where_publication(list)
|> Repo.get_by(attrs)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking author changes.
## Examples
iex> change_author(author)
%Ecto.Changeset{data: %Author{}}
"""
def change_author(%Author{} = author, attrs \\ %{}) do
Author.changeset(author, attrs)
end
@doc """
Creates an author.
## Examples
iex> create_author(%{field: value})
{:ok, %Author{}}
iex> create_author(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_author(attrs \\ %{}) do
%Author{}
|> Author.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates an author.
## Examples
iex> update_author(author, %{field: new_value})
{:ok, %Author{}}
iex> update_author(author, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_author(%Author{} = author, attrs) do
author
|> Author.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes an author.
## Examples
iex> delete_author(author)
{:ok, %Author{}}
iex> delete_author(author)
{:error, %Ecto.Changeset{}}
"""
def delete_author(%Author{} = author) do
Repo.delete(author)
end
## Post Context
@doc """
Returns the paginate list of posts.
## Examples
iex> paginate_posts()
%Scrivener.Page{entries: [%Post{}, ...], ...}
"""
def paginate_posts(attrs \\ []) do
Post
|> search(:title, attrs)
|> search(:slug, attrs)
|> search(:excerpt, attrs)
|> search(:html, attrs)
|> sort_by(attrs, {:desc, :published_at})
|> where_preload(attrs)
|> where_status(attrs)
|> where_publication(attrs)
|> Repo.paginate(attrs)
end
@doc """
Returns the list of posts.
## Examples
iex> list_posts()
[%Post{}, ...]
"""
def list_posts(attrs \\ []) do
Post
|> set_limit(attrs)
|> sort_by(attrs, {:desc, :published_at})
|> where_preload(%{"preload" => ~w(authors tags)a})
|> where_status(attrs)
|> where_publication(attrs)
|> Repo.all()
|> Enum.map(&Post.resolve/1)
end
@doc """
Gets a single post.
Raises `Ecto.NoResultsError` if the Post does not exist.
## Examples
iex> get_post!(123)
%Post{}
iex> get_post!(456)
** (Ecto.NoResultsError)
"""
def get_post!(id) when is_binary(id) do
Post
|> where_preload(%{"preload" => ~w(authors tags)a})
|> Repo.get(id)
|> Post.resolve()
end
def get_post!(list) do
attrs = Keyword.delete(list, :publication_slug)
Post
|> where_preload(%{"preload" => ~w(authors tags)a})
|> where_publication(list)
|> Repo.get_by(attrs)
|> Post.resolve()
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking post changes.
## Examples
iex> change_post(post)
%Ecto.Changeset{data: %Post{}}
"""
def change_post(%Post{} = post, attrs \\ %{}) do
Post.changeset(post, attrs)
end
@doc """
Creates a post.
## Examples
iex> create_post(%{field: value})
{:ok, %Post{}}
iex> create_post(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_post(attrs \\ %{}) do
%Post{}
|> Post.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a post.
## Examples
iex> update_post(post, %{field: new_value})
{:ok, %Post{}}
iex> update_post(post, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_post(%Post{} = post, attrs) do
post
|> Post.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a post.
## Examples
iex> delete_post(post)
{:ok, %Post{}}
iex> delete_post(post)
{:error, %Ecto.Changeset{}}
"""
def delete_post(%Post{} = post) do
Repo.delete(post)
end
## Publication Context
@doc """
Returns the list of publications.
## Examples
iex> list_publications()
[%Publication{}, ...]
"""
def list_publications(attrs \\ []) do
Publication
|> sort_by(attrs)
|> where_preload(attrs)
|> Repo.all()
end
@doc """
Gets a single publication.
Raises `Ecto.NoResultsError` if the Publication does not exist.
## Examples
iex> get_publication!(123)
%Publication{}
iex> get_publication!(456)
** (Ecto.NoResultsError)
"""
def get_publication!(id) when is_binary(id), do: Repo.get(Publication, id)
def get_publication!(list), do: Repo.get_by(Publication, list)
@doc """
Returns an `%Ecto.Changeset{}` for tracking publication changes.
## Examples
iex> change_publication(publication)
%Ecto.Changeset{data: %Publication{}}
"""
def change_publication(%Publication{} = publication, attrs \\ %{}) do
Publication.changeset(publication, attrs)
end
@doc """
Creates a publication.
## Examples
iex> create_publication(%{field: value})
{:ok, %Publication{}}
iex> create_publication(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_publication(attrs \\ %{}) do
%Publication{}
|> Publication.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a publication.
## Examples
iex> update_publication(publication, %{field: new_value})
{:ok, %Publication{}}
iex> update_publication(publication, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_publication(%Publication{} = publication, attrs) do
publication
|> Publication.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a publication.
## Examples
iex> delete_publication(publication)
{:ok, %Publication{}}
iex> delete_publication(publication)
{:error, %Ecto.Changeset{}}
"""
def delete_publication(%Publication{} = publication) do
Repo.delete(publication)
end
## Tag Context
@doc """
Returns the paginate list of tags.
## Examples
iex> paginate_tags()
%Scrivener.Page{entries: [%Tag{}, ...], ...}
"""
def paginate_tags(attrs \\ []) do
Tag
|> search(:name, attrs)
|> search(:slug, attrs)
|> search(:description, attrs)
|> sort_by(attrs)
|> where_status(attrs)
|> where_publication(attrs)
|> where_preload(%{"preload" => ~w(posts)a})
|> Repo.paginate(attrs)
end
@doc """
Returns the list of tags.
## Examples
iex> list_tags()
[%Tag{}, ...]
"""
def list_tags(attrs \\ []) do
Tag
|> sort_by(attrs)
|> where_preload(attrs)
|> where_publication(attrs)
|> Repo.all()
end
@doc """
Gets a single tag.
Raises `Ecto.NoResultsError` if the Tag does not exist.
## Examples
iex> get_tag!(123)
%Tag{}
iex> get_tag!(456)
** (Ecto.NoResultsError)
"""
def get_tag!(id) when is_binary(id), do: Repo.get(Tag, id)
def get_tag!(list) do
attrs = Keyword.delete(list, :publication_slug)
Tag
|> where_publication(list)
|> Repo.get_by(attrs)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking tag changes.
## Examples
iex> change_tag(tag)
%Ecto.Changeset{data: %Tag{}}
"""
def change_tag(%Tag{} = tag, attrs \\ %{}) do
Tag.changeset(tag, attrs)
end
@doc """
Creates a tag.
## Examples
iex> create_tag(%{field: value})
{:ok, %Tag{}}
iex> create_tag(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_tag(attrs \\ %{}) do
%Tag{}
|> Tag.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a tag.
## Examples
iex> update_tag(tag, %{field: new_value})
{:ok, %Tag{}}
iex> update_tag(tag, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_tag(%Tag{} = tag, attrs) do
tag
|> Tag.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a tag.
## Examples
iex> delete_tag(tag)
{:ok, %Tag{}}
iex> delete_tag(tag)
{:error, %Ecto.Changeset{}}
"""
def delete_tag(%Tag{} = tag) do
Repo.delete(tag)
end
## Redirect Context
@doc """
Returns the paginate list of redirects.
## Examples
iex> paginate_redirects()
%Scrivener.Page{entries: [%Redirect{}, ...], ...}
"""
def paginate_redirects(attrs \\ []) do
Redirect
|> search(:from, attrs)
|> search(:to, attrs)
|> sort_by(attrs, {:asc, :from})
|> where_publication(attrs)
|> where_preload(attrs)
|> Repo.paginate(attrs)
end
@doc """
Returns the list of redirects.
## Examples
iex> list_redirects()
[%Redirect{}, ...]
"""
def list_redirects(attrs \\ []) do
Redirect
|> sort_by(attrs, {:asc, :from})
|> where_publication(attrs)
|> where_preload(attrs)
|> Repo.all()
end
@doc """
Gets a single redirect.
Raises `Ecto.NoResultsError` if the Redirect does not exist.
## Examples
iex> get_redirect!(123)
%Redirect{}
iex> get_redirect!(456)
** (Ecto.NoResultsError)
"""
def get_redirect!(id) when is_binary(id), do: Repo.get(Redirect, id)
def get_redirect!(list) do
attrs = Keyword.delete(list, :publication_slug)
Redirect
|> where_publication(list)
|> Repo.get_by(attrs)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking redirect changes.
## Examples
iex> change_redirect(redirect)
%Ecto.Changeset{data: %Redirect{}}
"""
def change_redirect(%Redirect{} = redirect, attrs \\ %{}) do
Redirect.changeset(redirect, attrs)
end
@doc """
Creates a redirect.
## Examples
iex> create_redirect(%{field: value})
{:ok, %Redirect{}}
iex> create_redirect(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_redirect(attrs \\ %{}) do
%Redirect{}
|> Redirect.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a redirect.
## Examples
iex> update_redirect(redirect, %{field: new_value})
{:ok, %Redirect{}}
iex> update_redirect(redirect, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_redirect(%Redirect{} = redirect, attrs) do
redirect
|> Redirect.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a redirect.
## Examples
iex> delete_redirect(redirect)
{:ok, %Redirect{}}
iex> delete_redirect(redirect)
{:error, %Ecto.Changeset{}}
"""
def delete_redirect(%Redirect{} = redirect) do
Repo.delete(redirect)
end
def validate_params(params) do
cond do
is_nil(params["publication_id"]) ->
{:error, "Missing parameter :publication_id"}
is_nil(params["data"]) ->
{:error, "Missing parameter :data"}
true ->
{:ok, true}
end
end
def build_params(%{"publication_id" => publication_id, "data" => data}) do
data
|> Map.put("publication_id", publication_id)
|> parse_image("og_image", data["og_image"])
|> parse_image("twitter_image", data["twitter_image"])
|> parse_image("feature_image", data["feature_image"])
|> parse_image("profile_image", data["profile_image"])
|> parse_image("cover_image", data["cover_image"])
|> ImageComponent.build_images()
|> parse_authors()
|> parse_tags()
end
defp parse_image(data, field, url) when is_binary(url) do
filename = String.split(url, "/") |> List.last()
type =
filename
|> Path.extname()
|> String.trim_leading(".")
|> String.downcase()
with {:ok, path} <- DownloadHelpers.download_image(url),
true <- type in ~w(jpg png jpeg) do
Map.put(data, field, %Plug.Upload{
content_type: "image/#{type}",
filename: filename,
path: path
})
else
_error ->
Map.put(data, field, nil)
end
end
defp parse_image(data, _, _), do: data
defp parse_authors(%{"authors_names" => authors, "publication_id" => publication_id} = data)
when is_list(authors) do
authors_ids =
Enum.map(authors, &Literature.get_author!(name: &1, publication_id: publication_id).id)
Map.put(data, "authors_ids", authors_ids)
end
defp parse_authors(data), do: data
defp parse_tags(%{"tags_names" => tags, "publication_id" => publication_id} = data)
when is_list(tags) do
tags_ids = Enum.map(tags, &Literature.get_tag!(name: &1, publication_id: publication_id).id)
Map.put(data, "tags_ids", tags_ids)
end
defp parse_tags(data), do: data
def sort_tag_posts(post_ids, tag_id) do
existing_post_ids =
TagPost
|> filter(%{"post_id" => post_ids})
|> filter(%{"tag_id" => tag_id})
|> Repo.all()
|> Enum.map(& &1.post_id)
# Preserve order from input
params =
post_ids
|> Enum.filter(&(&1 in existing_post_ids))
|> Enum.with_index(1)
|> Enum.map(fn {post_id, index} ->
%{
post_id: post_id,
tag_id: tag_id,
position: index
}
end)
Repo.insert_all(TagPost, params,
conflict_target: [:tag_id, :post_id],
on_conflict: {:replace, [:position]}
)
end
def preload_tag_posts_with_position(tag_ids, status \\ nil) do
# Custom preloader to be used for preloader functions
# https://hexdocs.pm/ecto/Ecto.Query.html#preload/3-preload-functions
Post
|> where_status(%{"status" => status})
|> include_tag_post_custom_position(tag_ids)
|> sort_by(%{"sort_field" => "custom_position", "sort_direction" => "asc"})
|> Repo.all()
end
end