diff --git a/config/config.exs b/config/config.exs index d3a3bd7e..e8268ab5 100644 --- a/config/config.exs +++ b/config/config.exs @@ -9,6 +9,7 @@ use Mix.Config config :philomena, ecto_repos: [Philomena.Repo], + elasticsearch_url: "http://localhost:9200", password_pepper: "dn2e0EpZrvBLoxUM3gfQveBhjf0bG/6/bYhrOyq3L3hV9hdo/bimJ+irbDWsuXLP", image_url_root: "/img" @@ -21,6 +22,9 @@ config :philomena, :pow, config :bcrypt_elixir, log_rounds: 12 +config :elastix, + json_codec: Jason + # Configures the endpoint config :philomena, PhilomenaWeb.Endpoint, url: [host: "localhost"], diff --git a/lib/philomena/comments/comment.ex b/lib/philomena/comments/comment.ex index 7f07cd51..584003ff 100644 --- a/lib/philomena/comments/comment.ex +++ b/lib/philomena/comments/comment.ex @@ -3,7 +3,6 @@ defmodule Philomena.Comments.Comment do import Ecto.Changeset schema "comments" do - timestamps() end diff --git a/lib/philomena/elasticsearch.ex b/lib/philomena/elasticsearch.ex new file mode 100644 index 00000000..f8e39a96 --- /dev/null +++ b/lib/philomena/elasticsearch.ex @@ -0,0 +1,99 @@ +defmodule Philomena.Elasticsearch do + defmacro __using__(opts) do + definition = Keyword.fetch!(opts, :definition) + index_name = Keyword.fetch!(opts, :index_name) + + elastic_url = Application.get_env(:philomena, :elasticsearch_url) + + quote do + alias Philomena.Repo + import Ecto.Query, warn: false + + def create_index! do + Elastix.Index.create( + unquote(elastic_url), + unquote(index_name), + unquote(definition).mapping() + ) + end + + def delete_index! do + Elastix.Index.delete(unquote(elastic_url), unquote(index_name)) + end + + def index_document(doc) do + data = unquote(definition).as_json(doc) + + Elastix.Document.index(unquote(elastic_url), unquote(index_name), ["_doc"], data.id, data) + end + + def reindex(ecto_query, batch_size \\ 1000) do + ids = + ecto_query + |> exclude(:preload) + |> exclude(:order_by) + |> order_by(asc: :id) + |> select([m], m.id) + |> limit(^batch_size) + |> Repo.all() + + reindex(ecto_query, batch_size, ids) + end + + def reindex(ecto_query, batch_size, []), do: nil + + def reindex(ecto_query, batch_size, ids) do + lines = + ecto_query + |> where([m], m.id in ^ids) + |> Repo.all() + |> Enum.flat_map(fn m -> + doc = unquote(definition).as_json(m) + + [ + %{index: %{_index: unquote(index_name), _type: "_doc", _id: doc.id}}, + doc + ] + end) + + Elastix.Bulk.post(unquote(elastic_url), lines, + index: unquote(index_name), + httpoison_options: [timeout: 30_000] + ) + + ids = + ecto_query + |> exclude(:preload) + |> exclude(:order_by) + |> order_by(asc: :id) + |> where([m], m.id > ^Enum.max(ids)) + |> select([m], m.id) + |> limit(^batch_size) + |> Repo.all() + + reindex(ecto_query, batch_size, ids) + end + + def search_results(elastic_query) do + {:ok, %{body: results, status_code: 200}} = + Elastix.Search.search( + unquote(elastic_url), + unquote(index_name), + ["_doc"], + elastic_query + ) + + results + end + + def search_records(elastic_query, ecto_query \\ __MODULE__) do + results = search_results(elastic_query) + + ids = results["hits"]["hits"] |> Enum.map(&String.to_integer(&1["_id"])) + records = ecto_query |> where([m], m.id in ^ids) |> Repo.all() + + records |> Enum.sort_by(&Enum.find_index(ids, fn el -> el == &1.id end)) + end + end + end +end diff --git a/lib/philomena/galleries.ex b/lib/philomena/galleries.ex new file mode 100644 index 00000000..d1f54319 --- /dev/null +++ b/lib/philomena/galleries.ex @@ -0,0 +1,104 @@ +defmodule Philomena.Galleries do + @moduledoc """ + The Galleries context. + """ + + import Ecto.Query, warn: false + alias Philomena.Repo + + alias Philomena.Galleries.Gallery + + @doc """ + Returns the list of galleries. + + ## Examples + + iex> list_galleries() + [%Gallery{}, ...] + + """ + def list_galleries do + Repo.all(Gallery) + end + + @doc """ + Gets a single gallery. + + Raises `Ecto.NoResultsError` if the Gallery does not exist. + + ## Examples + + iex> get_gallery!(123) + %Gallery{} + + iex> get_gallery!(456) + ** (Ecto.NoResultsError) + + """ + def get_gallery!(id), do: Repo.get!(Gallery, id) + + @doc """ + Creates a gallery. + + ## Examples + + iex> create_gallery(%{field: value}) + {:ok, %Gallery{}} + + iex> create_gallery(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_gallery(attrs \\ %{}) do + %Gallery{} + |> Gallery.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a gallery. + + ## Examples + + iex> update_gallery(gallery, %{field: new_value}) + {:ok, %Gallery{}} + + iex> update_gallery(gallery, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_gallery(%Gallery{} = gallery, attrs) do + gallery + |> Gallery.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a Gallery. + + ## Examples + + iex> delete_gallery(gallery) + {:ok, %Gallery{}} + + iex> delete_gallery(gallery) + {:error, %Ecto.Changeset{}} + + """ + def delete_gallery(%Gallery{} = gallery) do + Repo.delete(gallery) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking gallery changes. + + ## Examples + + iex> change_gallery(gallery) + %Ecto.Changeset{source: %Gallery{}} + + """ + def change_gallery(%Gallery{} = gallery) do + Gallery.changeset(gallery, %{}) + end +end diff --git a/lib/philomena/galleries/gallery.ex b/lib/philomena/galleries/gallery.ex new file mode 100644 index 00000000..014716d5 --- /dev/null +++ b/lib/philomena/galleries/gallery.ex @@ -0,0 +1,24 @@ +defmodule Philomena.Galleries.Gallery do + use Ecto.Schema + import Ecto.Changeset + + schema "galleries" do + belongs_to :thumbnail, Philomena.Images.Image, source: :thumbnail_id + belongs_to :creator, Philomena.Users.User, source: :creator_id + + field :title, :string + field :spoiler_warning, :string + field :description, :string + field :image_count, :integer + field :order_position_asc, :boolean + + timestamps(inserted_at: :created_at) + end + + @doc false + def changeset(gallery, attrs) do + gallery + |> cast(attrs, []) + |> validate_required([]) + end +end diff --git a/lib/philomena/galleries/interaction.ex b/lib/philomena/galleries/interaction.ex new file mode 100644 index 00000000..5c5166ae --- /dev/null +++ b/lib/philomena/galleries/interaction.ex @@ -0,0 +1,18 @@ +defmodule Philomena.Galleries.Interaction do + use Ecto.Schema + import Ecto.Changeset + + schema "gallery_interactions" do + belongs_to :gallery, Philomena.Galleries.Gallery + belongs_to :image, Philomena.Images.Image + + field :position, :integer + end + + @doc false + def changeset(interaction, attrs) do + interaction + |> cast(attrs, []) + |> validate_required([]) + end +end diff --git a/lib/philomena/images/elasticsearch.ex b/lib/philomena/images/elasticsearch.ex new file mode 100644 index 00000000..dfef3855 --- /dev/null +++ b/lib/philomena/images/elasticsearch.ex @@ -0,0 +1,147 @@ +defmodule Philomena.Images.Elasticsearch do + def mapping do + %{ + settings: %{ + index: %{ + number_of_shards: 5, + max_result_window: 10_000_000 + } + }, + mappings: %{ + _doc: %{ + _all: %{enabled: false}, + dynamic: false, + properties: %{ + anonymous: %{type: "boolean"}, + aspect_ratio: %{type: "float"}, + comment_count: %{type: "integer"}, + commenters: %{type: "keyword"}, + created_at: %{type: "date"}, + deleted_by_user: %{type: "keyword"}, + deleted_by_user_id: %{type: "keyword"}, + deletion_reason: %{type: "text", analyzer: "snowball"}, + description: %{type: "text", analyzer: "snowball"}, + downvoter_ids: %{type: "keyword"}, + downvoters: %{type: "keyword"}, + downvotes: %{type: "integer"}, + faves: %{type: "integer"}, + favourited_by_user_ids: %{type: "keyword"}, + favourited_by_users: %{type: "keyword"}, + file_name: %{type: "keyword"}, + fingerprint: %{type: "keyword"}, + first_seen_at: %{type: "date"}, + height: %{type: "integer"}, + hidden_by_user_ids: %{type: "keyword"}, + hidden_by_users: %{type: "keyword"}, + hidden_from_users: %{type: "keyword"}, + id: %{type: "integer"}, + ip: %{type: "ip"}, + mime_type: %{type: "keyword"}, + orig_sha512_hash: %{type: "keyword"}, + original_format: %{type: "keyword"}, + score: %{type: "integer"}, + sha512_hash: %{type: "keyword"}, + source_url: %{type: "keyword"}, + tag_count: %{type: "integer"}, + tag_ids: %{type: "keyword"}, + tags: %{type: "text", analyzer: "keyword"}, + true_uploader: %{type: "keyword"}, + true_uploader_id: %{type: "keyword"}, + updated_at: %{type: "date"}, + uploader: %{type: "keyword"}, + uploader_id: %{type: "keyword"}, + upvoter_ids: %{type: "keyword"}, + upvoters: %{type: "keyword"}, + upvotes: %{type: "integer"}, + user_id: %{type: "keyword"}, + width: %{type: "integer"}, + wilson_score: %{type: "float"}, + galleries: %{ + type: "nested", + properties: %{ + id: %{type: "integer"}, + position: %{type: "integer"} + } + }, + namespaced_tags: %{ + properties: %{ + name: %{type: "keyword"}, + name_in_namespace: %{type: "keyword"}, + namespace: %{type: "keyword"} + } + } + } + } + } + } + end + + def as_json(image) do + %{ + id: image.id, + upvotes: image.upvotes_count, + downvotes: image.downvotes_count, + score: image.score, + faves: image.faves_count, + comment_count: image.comments_count, + width: image.image_width, + height: image.image_height, + tag_count: length(image.tags), + aspect_ratio: image.image_aspect_ratio, + wilson_score: wilson_score(image), + created_at: image.created_at, + updated_at: image.updated_at, + first_seen_at: image.first_seen_at, + ip: image.ip |> to_string, + tag_ids: image.tags |> Enum.map(& &1.id), + mime_type: image.image_mime_type, + uploader: if(!!image.user and !image.anonymous, do: String.downcase(image.user.name)), + true_uploader: if(!!image.user, do: String.downcase(image.user.name)), + source_url: image.source_url |> to_string |> String.downcase(), + file_name: image.image_name, + original_format: image.image_format, + fingerprint: image.fingerprint, + uploader_id: if(!!image.user_id and !image.anonymous, do: image.user_id), + true_uploader_id: image.user_id, + orig_sha512_hash: image.image_orig_sha512_hash, + sha512_hash: image.image_sha512_hash, + hidden_from_users: image.hidden_from_users, + anonymous: image.anonymous, + description: image.description, + deletion_reason: image.deletion_reason, + favourited_by_user_ids: image.favers |> Enum.map(& &1.id), + hidden_by_user_ids: image.hiders |> Enum.map(& &1.id), + upvoter_ids: image.upvoters |> Enum.map(& &1.id), + downvoter_ids: image.downvoters |> Enum.map(& &1.id), + deleted_by_user_id: image.deleter_id, + galleries: + image.gallery_interactions |> Enum.map(&%{id: &1.gallery_id, position: &1.position}), + namespaced_tags: %{ + name: image.tags |> Enum.flat_map(&([&1] ++ &1.aliases)) |> Enum.map(& &1.name) + }, + favourited_by_users: image.favers |> Enum.map(&String.downcase(&1.name)), + hidden_by_users: image.hiders |> Enum.map(&String.downcase(&1.name)), + upvoters: image.upvoters |> Enum.map(&String.downcase(&1.name)), + downvoters: image.downvoters |> Enum.map(&String.downcase(&1.name)), + deleted_by_user: if(!!image.deleter, do: image.deleter.name) + } + end + + def wilson_score(%{upvotes_count: upvotes, downvotes_count: downvotes}) + when upvotes > 0 or downvotes > 0 do + # Population size + n = (upvotes + downvotes) / 1 + + # Success proportion + p_hat = upvotes / n + + # z and z^2 values for CI upper 99.5% + z = 2.57583 + z2 = 6.634900189 + + (p_hat + z2 / (2 * n) - z * :math.sqrt((p_hat * (1 - p_hat) + z2 / (4 * n)) / n)) / + (1 + z2 / n) + end + + def wilson_score(_), do: 0 +end diff --git a/lib/philomena/images/fave.ex b/lib/philomena/images/fave.ex new file mode 100644 index 00000000..34447de5 --- /dev/null +++ b/lib/philomena/images/fave.ex @@ -0,0 +1,19 @@ +defmodule Philomena.Images.Fave do + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + schema "image_faves" do + belongs_to :user, Philomena.Users.User, primary_key: true + belongs_to :image, Philomena.Images.Image, primary_key: true + timestamps(inserted_at: :created_at, updated_at: false) + end + + @doc false + def changeset(fave, attrs) do + fave + |> cast(attrs, []) + |> validate_required([]) + end +end diff --git a/lib/philomena/images/hide.ex b/lib/philomena/images/hide.ex new file mode 100644 index 00000000..9343a2be --- /dev/null +++ b/lib/philomena/images/hide.ex @@ -0,0 +1,19 @@ +defmodule Philomena.Images.Hide do + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + schema "image_hides" do + belongs_to :user, Philomena.Users.User, primary_key: true + belongs_to :image, Philomena.Images.Image, primary_key: true + timestamps(inserted_at: :created_at, updated_at: false) + end + + @doc false + def changeset(hide, attrs) do + hide + |> cast(attrs, []) + |> validate_required([]) + end +end diff --git a/lib/philomena/images/image.ex b/lib/philomena/images/image.ex index 5ffbf7a4..85b8406c 100644 --- a/lib/philomena/images/image.ex +++ b/lib/philomena/images/image.ex @@ -1,11 +1,26 @@ defmodule Philomena.Images.Image do use Ecto.Schema + + use Philomena.Elasticsearch, + definition: Philomena.Images.Elasticsearch, + index_name: "images" + import Ecto.Changeset schema "images" do belongs_to :user, Philomena.Users.User belongs_to :deleter, Philomena.Users.User, source: :deleted_by_id - many_to_many :tags, Philomena.Tags.Tag, join_through: "image_taggings" + has_many :upvotes, Philomena.Images.Vote, where: [up: true] + has_many :downvotes, Philomena.Images.Vote, where: [up: false] + has_many :faves, Philomena.Images.Fave + has_many :hides, Philomena.Images.Hide + has_many :taggings, Philomena.Images.Tagging + has_many :gallery_interactions, Philomena.Galleries.Interaction + has_many :tags, through: [:taggings, :tag] + has_many :upvoters, through: [:upvotes, :user] + has_many :downvoters, through: [:downvotes, :user] + has_many :favers, through: [:faves, :user] + has_many :hiders, through: [:hides, :user] field :image, :string field :image_name, :string diff --git a/lib/philomena/images/tagging.ex b/lib/philomena/images/tagging.ex new file mode 100644 index 00000000..e2114a05 --- /dev/null +++ b/lib/philomena/images/tagging.ex @@ -0,0 +1,18 @@ +defmodule Philomena.Images.Tagging do + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + schema "image_taggings" do + belongs_to :image, Philomena.Images.Image, primary_key: true + belongs_to :tag, Philomena.Tags.Tag, primary_key: true + end + + @doc false + def changeset(tagging, attrs) do + tagging + |> cast(attrs, []) + |> validate_required([]) + end +end diff --git a/lib/philomena/images/vote.ex b/lib/philomena/images/vote.ex new file mode 100644 index 00000000..b2c3bea0 --- /dev/null +++ b/lib/philomena/images/vote.ex @@ -0,0 +1,20 @@ +defmodule Philomena.Images.Vote do + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + schema "image_votes" do + belongs_to :user, Philomena.Users.User, primary_key: true + belongs_to :image, Philomena.Images.Image, primary_key: true + field :up, :boolean + timestamps(inserted_at: :created_at, updated_at: false) + end + + @doc false + def changeset(vote, attrs) do + vote + |> cast(attrs, []) + |> validate_required([]) + end +end diff --git a/lib/philomena/tags/tag.ex b/lib/philomena/tags/tag.ex index 43ab3fac..6ebe122f 100644 --- a/lib/philomena/tags/tag.ex +++ b/lib/philomena/tags/tag.ex @@ -3,6 +3,9 @@ defmodule Philomena.Tags.Tag do import Ecto.Changeset schema "tags" do + belongs_to :aliased_tag, Philomena.Tags.Tag, source: :aliased_tag_id + has_many :aliases, Philomena.Tags.Tag, foreign_key: :aliased_tag_id + field :slug, :string field :name, :string field :category, :string diff --git a/lib/philomena_web/views/app_view.ex b/lib/philomena_web/views/app_view.ex index 4feb3037..b0af7be8 100644 --- a/lib/philomena_web/views/app_view.ex +++ b/lib/philomena_web/views/app_view.ex @@ -26,20 +26,21 @@ defmodule PhilomenaWeb.AppView do months = abs(div(days, 30)) years = abs(div(days, 365)) - words = cond do - seconds < 45 -> String.replace(@time_strings[:seconds], "%d", to_string(seconds)) - seconds < 90 -> String.replace(@time_strings[:minute], "%d", to_string(1)) - minutes < 45 -> String.replace(@time_strings[:minutes], "%d", to_string(minutes)) - minutes < 90 -> String.replace(@time_strings[:hour], "%d", to_string(1)) - hours < 24 -> String.replace(@time_strings[:hours], "%d", to_string(hours)) - hours < 42 -> String.replace(@time_strings[:day], "%d", to_string(1)) - days < 30 -> String.replace(@time_strings[:days], "%d", to_string(days)) - days < 45 -> String.replace(@time_strings[:month], "%d", to_string(1)) - days < 365 -> String.replace(@time_strings[:months], "%d", to_string(months)) - days < 548 -> String.replace(@time_strings[:year], "%d", to_string(1)) - true -> String.replace(@time_strings[:years], "%d", to_string(years)) - end + words = + cond do + seconds < 45 -> String.replace(@time_strings[:seconds], "%d", to_string(seconds)) + seconds < 90 -> String.replace(@time_strings[:minute], "%d", to_string(1)) + minutes < 45 -> String.replace(@time_strings[:minutes], "%d", to_string(minutes)) + minutes < 90 -> String.replace(@time_strings[:hour], "%d", to_string(1)) + hours < 24 -> String.replace(@time_strings[:hours], "%d", to_string(hours)) + hours < 42 -> String.replace(@time_strings[:day], "%d", to_string(1)) + days < 30 -> String.replace(@time_strings[:days], "%d", to_string(days)) + days < 45 -> String.replace(@time_strings[:month], "%d", to_string(1)) + days < 365 -> String.replace(@time_strings[:months], "%d", to_string(months)) + days < 548 -> String.replace(@time_strings[:year], "%d", to_string(1)) + true -> String.replace(@time_strings[:years], "%d", to_string(years)) + end content_tag(:time, "#{words} #{relation}", datetime: time |> NaiveDateTime.to_iso8601()) end -end \ No newline at end of file +end diff --git a/mix.exs b/mix.exs index 3f4fdff7..f3ceb164 100644 --- a/mix.exs +++ b/mix.exs @@ -48,7 +48,8 @@ defmodule Philomena.MixProject do {:pow, "~> 1.0.11"}, {:bcrypt_elixir, "~> 2.0"}, {:pot, "~> 0.10.1"}, - {:secure_compare, "~> 0.1.0"} + {:secure_compare, "~> 0.1.0"}, + {:elastix, "~> 0.7.1"} ] end diff --git a/mix.lock b/mix.lock index 2844357f..c9a7a73f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,6 @@ %{ "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.0.3", "64e0792d5b5064391927bf3b8e436994cafd18ca2d2b76dea5c76e0adcf66b7c", [:make, :mix], [{:comeonin, "~> 5.1", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "comeonin": {:hex, :comeonin, "5.1.2", "fbbbbbfcf0f0e9900c0336d16c8d462edf838ba1759577e29cc5fbd7c28a4540", [:mix], [], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, @@ -9,12 +10,19 @@ "ecto": {:hex, :ecto, "3.1.7", "fa21d06ef56cdc2fdaa62574e8c3ba34a2751d44ea34c30bc65f0728421043e5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "ecto_network": {:hex, :ecto_network, "1.1.0", "7062004b9324ff13e50c02dab84877f8a55e06db9eabbf2d04bda21da6fc6e8a", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 0.0.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.14.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm"}, "ecto_sql": {:hex, :ecto_sql, "3.1.6", "1e80e30d16138a729c717f73dcb938590bcdb3a4502f3012414d0cbb261045d8", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0 or ~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, + "elastix": {:hex, :elastix, "0.7.1", "8e199a764a0bc018e0a97afeea950a8069b988867d87f8d25ae121d8b3288612", [:mix], [{:httpoison, "~> 1.4", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}, {:retry, "~> 0.8", [hex: :retry, repo: "hexpm", optional: false]}], "hexpm"}, "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm"}, "file_system": {:hex, :file_system, "0.2.7", "e6f7f155970975789f26e77b8b8d8ab084c59844d8ecfaf58cbda31c494d14aa", [:mix], [], "hexpm"}, "gettext": {:hex, :gettext, "0.17.0", "abe21542c831887a2b16f4c94556db9c421ab301aee417b7c4fbde7fbdbe01ec", [:mix], [], "hexpm"}, + "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "httpoison": {:hex, :httpoison, "1.5.1", "0f55b5b673b03c5c327dac7015a67cb571b99b631acc0bc1b0b98dcd6b9f2104", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, "neotoma": {:hex, :neotoma, "1.7.3", "d8bd5404b73273989946e4f4f6d529e5c2088f5fa1ca790b4dbe81f4be408e61", [:rebar], [], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "pbkdf2": {:hex, :pbkdf2, "2.0.0", "11c23279fded5c0027ab3996cfae77805521d7ef4babde2bd7ec04a9086cf499", [:rebar3], [], "hexpm"}, "phoenix": {:hex, :phoenix, "1.4.9", "746d098e10741c334d88143d3c94cab1756435f94387a63441792e66ec0ee974", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, @@ -29,7 +37,10 @@ "pot": {:hex, :pot, "0.10.1", "af7dc220fd45478719b821fb4c1222975132516478483213507f95026298d8ab", [:rebar3], [], "hexpm"}, "pow": {:hex, :pow, "1.0.11", "f5ef721ac17d2bf8f9dd92f5d40fa0b96512d24b91a26603147754034e3a6101", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3.0 or ~> 1.4.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 2.0.0 and <= 3.0.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, ">= 1.5.0 and < 2.0.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, + "retry": {:hex, :retry, "0.13.0", "bb9b2713f70f39337837852337ad280c77662574f4fb852a8386c269f3d734c4", [:mix], [], "hexpm"}, "secure_compare": {:hex, :secure_compare, "0.1.0", "01b3c93c8edb696e8a5b38397ed48e10958c8a5ec740606656445bcbec0aadb8", [:mix], [], "hexpm"}, "slime": {:hex, :slime, "1.2.0", "d46ede53c96b743dfdd23821268dc9b01f04ffea65d9d57c4e3d9200b162df02", [:mix], [{:neotoma, "~> 1.7", [hex: :neotoma, repo: "hexpm", optional: false]}], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, "telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, } diff --git a/test/philomena/galleries_test.exs b/test/philomena/galleries_test.exs new file mode 100644 index 00000000..69de771e --- /dev/null +++ b/test/philomena/galleries_test.exs @@ -0,0 +1,62 @@ +defmodule Philomena.GalleriesTest do + use Philomena.DataCase + + alias Philomena.Galleries + + describe "galleries" do + alias Philomena.Galleries.Gallery + + @valid_attrs %{} + @update_attrs %{} + @invalid_attrs %{} + + def gallery_fixture(attrs \\ %{}) do + {:ok, gallery} = + attrs + |> Enum.into(@valid_attrs) + |> Galleries.create_gallery() + + gallery + end + + test "list_galleries/0 returns all galleries" do + gallery = gallery_fixture() + assert Galleries.list_galleries() == [gallery] + end + + test "get_gallery!/1 returns the gallery with given id" do + gallery = gallery_fixture() + assert Galleries.get_gallery!(gallery.id) == gallery + end + + test "create_gallery/1 with valid data creates a gallery" do + assert {:ok, %Gallery{} = gallery} = Galleries.create_gallery(@valid_attrs) + end + + test "create_gallery/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Galleries.create_gallery(@invalid_attrs) + end + + test "update_gallery/2 with valid data updates the gallery" do + gallery = gallery_fixture() + assert {:ok, %Gallery{} = gallery} = Galleries.update_gallery(gallery, @update_attrs) + end + + test "update_gallery/2 with invalid data returns error changeset" do + gallery = gallery_fixture() + assert {:error, %Ecto.Changeset{}} = Galleries.update_gallery(gallery, @invalid_attrs) + assert gallery == Galleries.get_gallery!(gallery.id) + end + + test "delete_gallery/1 deletes the gallery" do + gallery = gallery_fixture() + assert {:ok, %Gallery{}} = Galleries.delete_gallery(gallery) + assert_raise Ecto.NoResultsError, fn -> Galleries.get_gallery!(gallery.id) end + end + + test "change_gallery/1 returns a gallery changeset" do + gallery = gallery_fixture() + assert %Ecto.Changeset{} = Galleries.change_gallery(gallery) + end + end +end diff --git a/test/philomena_web/controllers/comment_controller_test.exs b/test/philomena_web/controllers/comment_controller_test.exs index 07b901c4..1fd73c8b 100644 --- a/test/philomena_web/controllers/comment_controller_test.exs +++ b/test/philomena_web/controllers/comment_controller_test.exs @@ -75,6 +75,7 @@ defmodule PhilomenaWeb.CommentControllerTest do test "deletes chosen comment", %{conn: conn, comment: comment} do conn = delete(conn, Routes.comment_path(conn, :delete, comment)) assert redirected_to(conn) == Routes.comment_path(conn, :index) + assert_error_sent 404, fn -> get(conn, Routes.comment_path(conn, :show, comment)) end diff --git a/test/philomena_web/controllers/tag_controller_test.exs b/test/philomena_web/controllers/tag_controller_test.exs index 6b7f0670..f5143c32 100644 --- a/test/philomena_web/controllers/tag_controller_test.exs +++ b/test/philomena_web/controllers/tag_controller_test.exs @@ -75,6 +75,7 @@ defmodule PhilomenaWeb.TagControllerTest do test "deletes chosen tag", %{conn: conn, tag: tag} do conn = delete(conn, Routes.tag_path(conn, :delete, tag)) assert redirected_to(conn) == Routes.tag_path(conn, :index) + assert_error_sent 404, fn -> get(conn, Routes.tag_path(conn, :show, tag)) end diff --git a/vagrant/install.bash b/vagrant/install.bash index 23b7fdf1..237191a2 100644 --- a/vagrant/install.bash +++ b/vagrant/install.bash @@ -24,9 +24,9 @@ fi # Necessary for apt and elasticsearch to succeed install_packages apt-transport-https default-jre-headless -if [ ! -f /etc/apt/sources.list.d/elasticsearch-7.x.list ]; then +if [ ! -f /etc/apt/sources.list.d/elasticsearch-6.x.list ]; then add_key https://packages.elastic.co/GPG-KEY-elasticsearch - echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" > /etc/apt/sources.list.d/elasticsearch-7.x.list + echo "deb https://artifacts.elastic.co/packages/6.x/apt stable main" > /etc/apt/sources.list.d/elasticsearch-6.x.list fi if [ ! -f /etc/apt/sources.list.d/pgdg.list ]; then