feat: Add models for content
This commit is contained in:
parent
e73f2c8996
commit
36589ecfee
54 changed files with 2699 additions and 0 deletions
34
apps/content/.gitignore
vendored
Normal file
34
apps/content/.gitignore
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where 3rd-party dependencies like ExDoc output generated docs.
|
||||
/doc/
|
||||
|
||||
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||
/.fetch
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
auth_web-*.tar
|
||||
|
||||
# If NPM crashes, it generates a log, let's ignore it too.
|
||||
npm-debug.log
|
||||
|
||||
# The directory NPM downloads your dependencies sources to.
|
||||
/assets/node_modules/
|
||||
|
||||
# Since we are building assets from assets/,
|
||||
# we ignore priv/static. You may want to comment
|
||||
# this depending on your deployment strategy.
|
||||
/priv/static/
|
26
apps/content/config/config.exs
Normal file
26
apps/content/config/config.exs
Normal file
|
@ -0,0 +1,26 @@
|
|||
# This file is responsible for configuring your application
|
||||
# and its dependencies with the aid of the Mix.Config module.
|
||||
#
|
||||
# This configuration file is loaded before any dependency and
|
||||
# is restricted to this project.
|
||||
use Mix.Config
|
||||
|
||||
# General application configuration
|
||||
config :content,
|
||||
ecto_repos: [Content.Repo]
|
||||
|
||||
config :phoenix, :json_library, Jason
|
||||
|
||||
# Configures Elixir's Logger
|
||||
config :logger, :console,
|
||||
format: "$time $metadata[$level] $message\n",
|
||||
metadata: [:user_id]
|
||||
|
||||
config :content, Content.Scheduler,
|
||||
jobs: [
|
||||
{"@hourly", {ContentWeb.Sitemaps, :generate, []}}
|
||||
]
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env}.exs"
|
76
apps/content/config/dev.exs
Normal file
76
apps/content/config/dev.exs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use Mix.Config
|
||||
|
||||
# Configure your database
|
||||
config :content, Content.Repo,
|
||||
username: "postgres",
|
||||
password: "postgres",
|
||||
database: "content_dev",
|
||||
hostname: "localhost",
|
||||
show_sensitive_data_on_connection_error: true,
|
||||
pool_size: 10
|
||||
|
||||
# For development, we disable any cache and enable
|
||||
# debugging and code reloading.
|
||||
#
|
||||
# The watchers configuration can be used to run external
|
||||
# watchers to your application. For example, we use it
|
||||
# with webpack to recompile .js and .css sources.
|
||||
config :content, ContentWeb.Endpoint,
|
||||
http: [port: 4000],
|
||||
debug_errors: true,
|
||||
code_reloader: true,
|
||||
check_origin: false,
|
||||
watchers: [
|
||||
node: [
|
||||
"node_modules/webpack/bin/webpack.js",
|
||||
"--mode",
|
||||
"development",
|
||||
"--watch-stdin",
|
||||
cd: Path.expand("../assets", __DIR__)
|
||||
]
|
||||
]
|
||||
|
||||
# ## SSL Support
|
||||
#
|
||||
# In order to use HTTPS in development, a self-signed
|
||||
# certificate can be generated by running the following
|
||||
# Mix task:
|
||||
#
|
||||
# mix phx.gen.cert
|
||||
#
|
||||
# Note that this task requires Erlang/OTP 20 or later.
|
||||
# Run `mix help phx.gen.cert` for more information.
|
||||
#
|
||||
# The `http:` config above can be replaced with:
|
||||
#
|
||||
# https: [
|
||||
# port: 4001,
|
||||
# cipher_suite: :strong,
|
||||
# keyfile: "priv/cert/selfsigned_key.pem",
|
||||
# certfile: "priv/cert/selfsigned.pem"
|
||||
# ],
|
||||
#
|
||||
# If desired, both `http:` and `https:` keys can be
|
||||
# configured to run both http and https servers on
|
||||
# different ports.
|
||||
|
||||
# Watch static and templates for browser reloading.
|
||||
config :content, ContentWeb.Endpoint,
|
||||
live_reload: [
|
||||
patterns: [
|
||||
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
|
||||
~r"priv/gettext/.*(po)$",
|
||||
~r"lib/content_web/(live|views)/.*(ex)$",
|
||||
~r"lib/content_web/templates/.*(eex)$"
|
||||
]
|
||||
]
|
||||
|
||||
# Do not include metadata nor timestamps in development logs
|
||||
config :logger, :console, format: "[$level] $message\n"
|
||||
|
||||
# Set a higher stacktrace during development. Avoid configuring such
|
||||
# in production as building large stacktraces may be expensive.
|
||||
config :phoenix, :stacktrace_depth, 20
|
||||
|
||||
# Initialize plugs at runtime for faster development compilation
|
||||
config :phoenix, :plug_init_mode, :runtime
|
12
apps/content/config/test.exs
Normal file
12
apps/content/config/test.exs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use Mix.Config
|
||||
|
||||
# Print only warnings and errors during test
|
||||
config :logger, level: :warn
|
||||
|
||||
config :content, Content.Repo,
|
||||
username: "postgres",
|
||||
password: "postgres",
|
||||
database: "content_test#{System.get_env("MIX_TEST_PARTITION")}",
|
||||
hostname: System.get_env("DATABASE_URL") || "localhost",
|
||||
show_sensitive_data_on_connection_error: true,
|
||||
pool: Ecto.Adapters.SQL.Sandbox
|
34
apps/content/lib/application.ex
Normal file
34
apps/content/lib/application.ex
Normal file
|
@ -0,0 +1,34 @@
|
|||
defmodule Content.Application do
|
||||
@moduledoc """
|
||||
The base module of the CMS application.
|
||||
"""
|
||||
use Application
|
||||
|
||||
# See https://hexdocs.pm/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
def start(_type, _args) do
|
||||
import Supervisor.Spec
|
||||
|
||||
# Define workers and child supervisors to be supervised
|
||||
children = [
|
||||
# Start the Ecto repository
|
||||
supervisor(Content.Repo, []),
|
||||
# Start the endpoint when the application starts
|
||||
# Start your own worker by calling: Content.Worker.start_link(arg1, arg2, arg3)
|
||||
# worker(Content.Worker, [arg1, arg2, arg3]),
|
||||
worker(Content.Scheduler, []),
|
||||
]
|
||||
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: Content.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
|
||||
# Tell Phoenix to update the endpoint configuration
|
||||
# whenever the application is updated.
|
||||
def config_change(changed, _new, removed) do
|
||||
Endpoint.config_change(changed, removed)
|
||||
:ok
|
||||
end
|
||||
end
|
40
apps/content/lib/attachment.ex
Normal file
40
apps/content/lib/attachment.ex
Normal file
|
@ -0,0 +1,40 @@
|
|||
defmodule Content.Attachment do
|
||||
@moduledoc """
|
||||
Helpers for dealing with "attachment"-type posts, which are generally media
|
||||
uploaded to the site e.g. images.
|
||||
"""
|
||||
alias Content.Post
|
||||
|
||||
def dimensions(attachment) do
|
||||
meta =
|
||||
attachment
|
||||
|> Post.metas_map
|
||||
|
||||
deserialization_results =
|
||||
meta["_wp_attachment_metadata"]
|
||||
|> PhpSerializer.unserialize
|
||||
|
||||
case deserialization_results do
|
||||
{:ok, info} ->
|
||||
%{
|
||||
width: info |> Enum.find(fn {key, _} -> key == "width" end) |> elem(1),
|
||||
height: info |> Enum.find(fn {key, _} -> key == "height" end) |> elem(1)
|
||||
}
|
||||
{:error, _} ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def vertical?(attachment) do
|
||||
case dimensions(attachment) do
|
||||
%{width: width, height: height} ->
|
||||
if width < height do
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
178
apps/content/lib/commands/update_menu.ex
Normal file
178
apps/content/lib/commands/update_menu.ex
Normal file
|
@ -0,0 +1,178 @@
|
|||
defmodule Content.UpdateMenu do
|
||||
alias Content.{Menu, Post, Postmeta, Repo, TermRelationship}
|
||||
alias Ecto.Multi
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
def run(id, new_menu_params) do
|
||||
current_posts = Menu.nav_menu_items_for_id(id)
|
||||
post_ids_in_new_menu = recursive_post_ids(new_menu_params)
|
||||
deleted_post_ids =
|
||||
current_posts
|
||||
|> Enum.reject(& &1."ID" in post_ids_in_new_menu)
|
||||
|> Enum.map(& &1."ID")
|
||||
|
||||
Multi.new()
|
||||
|> process_nodes(id, 0, new_menu_params |> add_order())
|
||||
|> Multi.delete_all(:stale_nodes, from(p in Post, where: p."ID" in ^deleted_post_ids))
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
def add_order(tree) do
|
||||
{_next_order, nodes} = add_order_starting_at(tree, 1)
|
||||
|
||||
nodes
|
||||
end
|
||||
|
||||
def add_order_starting_at(tree, starting_at) do
|
||||
tree
|
||||
|> Enum.reduce({starting_at, []}, fn node, {order, new_nodes} ->
|
||||
{next_order, new_child_nodes} = add_order_starting_at(node["children"], order + 1)
|
||||
new_node =
|
||||
node
|
||||
|> Map.merge(%{
|
||||
"order" => order,
|
||||
"children" => new_child_nodes,
|
||||
})
|
||||
{next_order, new_nodes ++ [new_node]}
|
||||
end)
|
||||
end
|
||||
|
||||
defp process_nodes(multi, menu_id, parent_id, nodes) do
|
||||
nodes
|
||||
|> Enum.reduce(multi, fn node, m ->
|
||||
case node["post_id"] do
|
||||
nil ->
|
||||
create_node(m, menu_id, parent_id, node)
|
||||
_id ->
|
||||
update_node(m, menu_id, parent_id, node)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp create_node(multi, menu_id, parent_id, node) do
|
||||
post =
|
||||
Post.changeset(
|
||||
%Post{},
|
||||
%{
|
||||
post_author: 1,
|
||||
post_title: node["title"],
|
||||
post_status: "publish",
|
||||
comment_status: "closed",
|
||||
ping_status: "closed",
|
||||
menu_order: node["order"],
|
||||
post_type: "nav_menu_item",
|
||||
comment_count: 0,
|
||||
}
|
||||
)
|
||||
|
||||
step_name = "#{parent_id}.create_node.#{node["order"]}"
|
||||
|
||||
multi
|
||||
|> Multi.insert(step_name, post)
|
||||
|> Multi.run("#{step_name}.term_relationship", fn _repo, %{^step_name => post} ->
|
||||
tr =
|
||||
TermRelationship.changeset(
|
||||
%TermRelationship{},
|
||||
%{
|
||||
object_id: post."ID",
|
||||
term_taxonomy_id: menu_id,
|
||||
term_order: 0,
|
||||
}
|
||||
)
|
||||
|
||||
Repo.insert(tr)
|
||||
end)
|
||||
|> Multi.merge(fn %{^step_name => post} ->
|
||||
Multi.new()
|
||||
|> insert_metas(type_of_node(node), post, parent_id, node)
|
||||
end)
|
||||
|> Multi.merge(fn %{^step_name => post} ->
|
||||
Multi.new()
|
||||
|> process_nodes(menu_id, post."ID", node["children"])
|
||||
end)
|
||||
end
|
||||
|
||||
defp insert_metas(multi, "post", post, parent_id, node) do
|
||||
multi
|
||||
|> update_meta(post."ID", "_menu_item_type", "post_type")
|
||||
|> update_meta(post."ID", "_menu_item_object", "page")
|
||||
|> update_meta(post."ID", "_menu_item_object_id", node["target_id"])
|
||||
|> update_meta(post."ID", "_menu_item_menu_item_parent", parent_id)
|
||||
end
|
||||
|
||||
defp insert_metas(multi, "category", post, parent_id, node) do
|
||||
multi
|
||||
|> update_meta(post."ID", "_menu_item_type", "taxonomy")
|
||||
|> update_meta(post."ID", "_menu_item_object", "category")
|
||||
|> update_meta(post."ID", "_menu_item_object_id", node["target_id"])
|
||||
|> update_meta(post."ID", "_menu_item_menu_item_parent", parent_id)
|
||||
end
|
||||
|
||||
defp insert_metas(multi, "link", post, parent_id, node) do
|
||||
multi
|
||||
|> update_meta(post."ID", "_menu_item_type", "custom")
|
||||
|> update_meta(post."ID", "_menu_item_object", "custom")
|
||||
|> update_meta(post."ID", "_menu_item_object_id", post."ID")
|
||||
|> update_meta(post."ID", "_menu_item_url", node["url"])
|
||||
|> update_meta(post."ID", "_menu_item_menu_item_parent", parent_id)
|
||||
end
|
||||
|
||||
defp type_of_node(%{"url" => url}) when url != nil, do: "link"
|
||||
defp type_of_node(%{"related_item" => %{"resource" => "posts"}}), do: "post"
|
||||
defp type_of_node(%{"related_item" => %{"resource" => "category"}}), do: "category"
|
||||
|
||||
defp update_node(multi, menu_id, parent_id, node) do
|
||||
multi
|
||||
|> update_meta(node["post_id"], "_menu_item_menu_item_parent", parent_id)
|
||||
|> update_order(node["post_id"], node["order"])
|
||||
|> process_nodes(menu_id, node["post_id"], node["children"])
|
||||
end
|
||||
|
||||
defp update_meta(multi, post_id, meta_key, new_value) when is_integer(new_value),
|
||||
do: update_meta(multi, post_id, meta_key, new_value |> Integer.to_string())
|
||||
|
||||
defp update_meta(multi, post_id, meta_key, new_value) do
|
||||
step_name = "#{post_id}.update_meta.#{meta_key}"
|
||||
type = Postmeta.__schema__(:type, :meta_value)
|
||||
cast_value = Ecto.Type.cast(type, new_value)
|
||||
|
||||
Postmeta
|
||||
|> where([pm], pm.meta_key == ^meta_key)
|
||||
|> where([pm], pm.post_id == ^post_id)
|
||||
|> Repo.one()
|
||||
|> case do
|
||||
nil ->
|
||||
multi
|
||||
|> Multi.insert(
|
||||
step_name,
|
||||
Postmeta.changeset(
|
||||
%Postmeta{},
|
||||
%{
|
||||
post_id: post_id,
|
||||
meta_key: meta_key,
|
||||
meta_value: new_value
|
||||
}
|
||||
)
|
||||
)
|
||||
%{meta_value: ^cast_value} ->
|
||||
# No change needed
|
||||
multi
|
||||
meta ->
|
||||
multi
|
||||
|> Multi.update(step_name, Postmeta.changeset(meta, %{meta_value: new_value}))
|
||||
end
|
||||
end
|
||||
|
||||
defp update_order(multi, post_id, new_order) do
|
||||
step_name = "#{post_id}.update_order"
|
||||
|
||||
multi
|
||||
|> Multi.update_all(step_name, from(p in Post, where: p."ID" == ^post_id), [set: [menu_order: new_order]])
|
||||
end
|
||||
|
||||
defp recursive_post_ids(params) do
|
||||
params
|
||||
|> Enum.flat_map(& [&1["post_id"]|recursive_post_ids(&1["children"])])
|
||||
end
|
||||
end
|
54
apps/content/lib/comment.ex
Normal file
54
apps/content/lib/comment.ex
Normal file
|
@ -0,0 +1,54 @@
|
|||
defmodule Content.Comment do
|
||||
@moduledoc """
|
||||
A comment on the site.
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Content.{Post}
|
||||
|
||||
@primary_key {:comment_ID, :id, autogenerate: true}
|
||||
@derive {Phoenix.Param, key: :comment_ID}
|
||||
schema "wp_comments" do
|
||||
belongs_to :post, Post, foreign_key: :comment_post_ID, references: :ID
|
||||
field :comment_author, :string
|
||||
field :comment_author_email, :string
|
||||
field :comment_author_url, :string
|
||||
field :comment_author_IP, :string
|
||||
field :comment_date, :naive_datetime
|
||||
field :comment_date_gmt, :naive_datetime
|
||||
field :comment_content, :string
|
||||
field :comment_karma, :integer
|
||||
field :comment_approved, :string
|
||||
field :comment_agent, :string
|
||||
field :comment_type, :string
|
||||
field :comment_parent, :integer, default: 0
|
||||
field :user_id, :integer
|
||||
end
|
||||
|
||||
def changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> Map.merge(%{
|
||||
comment_date: NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second),
|
||||
comment_date_gmt: NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second),
|
||||
comment_approved: "1"
|
||||
})
|
||||
|> cast(params, [
|
||||
:comment_ID,
|
||||
:comment_post_ID,
|
||||
:comment_author,
|
||||
:comment_author_email,
|
||||
:comment_author_url,
|
||||
:comment_author_IP,
|
||||
:comment_date,
|
||||
:comment_date_gmt,
|
||||
:comment_content,
|
||||
:comment_karma,
|
||||
:comment_approved,
|
||||
:comment_agent,
|
||||
:comment_type,
|
||||
:comment_parent,
|
||||
:user_id
|
||||
])
|
||||
|> validate_required([:comment_content])
|
||||
end
|
||||
end
|
19
apps/content/lib/commentmeta.ex
Normal file
19
apps/content/lib/commentmeta.ex
Normal file
|
@ -0,0 +1,19 @@
|
|||
defmodule Content.Commentmeta do
|
||||
@moduledoc """
|
||||
A piece of metadata about a comment on the site.
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:meta_id, :id, autogenerate: true}
|
||||
schema "wp_commentmeta" do
|
||||
field :comment_id, :integer
|
||||
field :meta_key, :string
|
||||
field :meta_value, :string
|
||||
end
|
||||
|
||||
def changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:meta_id, :comment_id, :meta_key, :meta_value])
|
||||
end
|
||||
end
|
107
apps/content/lib/comments.ex
Normal file
107
apps/content/lib/comments.ex
Normal file
|
@ -0,0 +1,107 @@
|
|||
defmodule Content.Comments do
|
||||
@moduledoc """
|
||||
Functions for presenting comments on the site.
|
||||
"""
|
||||
import Ecto.Query, warn: false
|
||||
alias Content.Comment
|
||||
alias Content.Repo
|
||||
|
||||
def children(parent_comment_id, array_of_comments) do
|
||||
array_of_comments
|
||||
|> Enum.filter(&(&1.comment_parent == parent_comment_id))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of comments.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_comments()
|
||||
[%Comment{}, ...]
|
||||
|
||||
"""
|
||||
def list_comments do
|
||||
Repo.all(Comment)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single comment.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the comment does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_comment!(123)
|
||||
%Comment{}
|
||||
|
||||
iex> get_comment!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_comment!(id), do: Repo.get!(Comment, id)
|
||||
|
||||
@doc """
|
||||
Creates a comment.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_comment(%{field: value})
|
||||
{:ok, %Comment{}}
|
||||
|
||||
iex> create_comment(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_comment(attrs \\ %{}) do
|
||||
%Comment{}
|
||||
|> Comment.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a comment.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_comment(comment, %{field: new_value})
|
||||
{:ok, %Comment{}}
|
||||
|
||||
iex> update_comment(comment, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_comment(%Comment{} = comment, attrs) do
|
||||
comment
|
||||
|> Comment.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a Comment.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_comment(comment)
|
||||
{:ok, %Comment{}}
|
||||
|
||||
iex> delete_comment(comment)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_comment(%Comment{} = comment) do
|
||||
Repo.delete(comment)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking comment changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_comment(comment)
|
||||
%Ecto.Changeset{source: %Comment{}}
|
||||
|
||||
"""
|
||||
def change_comment(%Comment{} = comment) do
|
||||
Comment.changeset(comment, %{})
|
||||
end
|
||||
end
|
42
apps/content/lib/link.ex
Normal file
42
apps/content/lib/link.ex
Normal file
|
@ -0,0 +1,42 @@
|
|||
defmodule Content.Link do
|
||||
@moduledoc """
|
||||
A link for the (deprecated) link roll feature.
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:link_id, :id, autogenerate: true}
|
||||
schema "wp_links" do
|
||||
field :link_url, :string
|
||||
field :link_name, :string
|
||||
field :link_image, :string
|
||||
field :link_target, :string
|
||||
field :link_description, :string
|
||||
field :link_visible, :string
|
||||
field :link_owner, :integer
|
||||
field :link_rating, :integer
|
||||
field :link_updated, :naive_datetime
|
||||
field :link_rel, :string
|
||||
field :link_notes, :string
|
||||
field :link_rss, :string
|
||||
end
|
||||
|
||||
def changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [
|
||||
:link_id,
|
||||
:link_url,
|
||||
:link_name,
|
||||
:link_image,
|
||||
:link_target,
|
||||
:link_description,
|
||||
:link_visible,
|
||||
:link_owner,
|
||||
:link_rating,
|
||||
:link_updated,
|
||||
:link_rel,
|
||||
:link_notes,
|
||||
:link_rss
|
||||
])
|
||||
end
|
||||
end
|
167
apps/content/lib/menu.ex
Normal file
167
apps/content/lib/menu.ex
Normal file
|
@ -0,0 +1,167 @@
|
|||
defmodule Content.Menu do
|
||||
@moduledoc """
|
||||
Module for retrieving, manipulating, and processing navigation menus.
|
||||
"""
|
||||
alias Content.{Option, Post, Repo, Term, TermRelationship}
|
||||
import Ecto.Query
|
||||
|
||||
def put_menu_option(option_name, location_name, menu_id) do
|
||||
option =
|
||||
Option
|
||||
|> where(option_name: ^option_name)
|
||||
|> Repo.one()
|
||||
|> Kernel.||(%Option{option_name: option_name, option_value: "a:0:{}"})
|
||||
|
||||
value =
|
||||
option
|
||||
|> Option.parse_option_value
|
||||
|
||||
nav_menu_locations =
|
||||
value
|
||||
|> Enum.find(fn {key, _value} -> key == "nav_menu_locations" end)
|
||||
|> Kernel.||({"nav_menu_locations", []})
|
||||
|> elem(1)
|
||||
|
||||
new_nav_menu_locations =
|
||||
nav_menu_locations
|
||||
|> Enum.filter(fn {key, _value} -> key != location_name end)
|
||||
|> Kernel.++([{location_name, menu_id}])
|
||||
|
||||
new_value =
|
||||
value
|
||||
|> Enum.filter(fn {key, _value} -> key != "nav_menu_locations" end)
|
||||
|> Kernel.++([{"nav_menu_locations", new_nav_menu_locations}])
|
||||
|
||||
option
|
||||
|> Option.put_new_value(new_value)
|
||||
end
|
||||
|
||||
def get_menu_from_option_and_location(option_name, location_name) do
|
||||
option =
|
||||
Option
|
||||
|> where(option_name: ^option_name)
|
||||
|> Repo.one()
|
||||
|> Kernel.||(%Option{option_name: option_name, option_value: "a:0:{}"})
|
||||
|
||||
value =
|
||||
option
|
||||
|> Option.parse_option_value
|
||||
|
||||
menu_pair =
|
||||
value
|
||||
|> Enum.find(fn {key, _value} -> key == "nav_menu_locations" end)
|
||||
|> Kernel.||({"nav_menu_locations", []})
|
||||
|> elem(1)
|
||||
|> Enum.find(fn {key, _value} -> key == location_name end)
|
||||
|
||||
case menu_pair do
|
||||
{^location_name, menu_id} ->
|
||||
menu_id |> get_menu_from_id()
|
||||
nil ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_menu_from_id(menu_id) do
|
||||
menu_id
|
||||
|> nav_menu_items_for_id()
|
||||
|> arrange_menu_item_posts()
|
||||
end
|
||||
|
||||
def nav_menu_items_for_id(menu_id) do
|
||||
Post
|
||||
|> join(
|
||||
:inner,
|
||||
[p],
|
||||
tr in TermRelationship,
|
||||
on: p."ID" == tr.object_id
|
||||
)
|
||||
|> order_by(:menu_order)
|
||||
|> preload(:metas)
|
||||
|> where([p, tr], tr.term_taxonomy_id == ^menu_id)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
defp arrange_menu_item_posts(nav_posts, parent_id \\ "0", nav_to_post_map \\ nil) do
|
||||
nav_to_post_map = nav_to_post_map || make_nav_to_post_map(nav_posts)
|
||||
|
||||
nav_posts
|
||||
|> Enum.filter(fn post ->
|
||||
meta_map = post |> Post.metas_map
|
||||
meta_map["_menu_item_menu_item_parent"] == parent_id
|
||||
end)
|
||||
|> Enum.map(fn post ->
|
||||
meta_map = post |> Post.metas_map
|
||||
related_item =
|
||||
if meta_map["_menu_item_object"] == "category" do
|
||||
item = nav_to_post_map["category/#{meta_map["_menu_item_object_id"]}"] || %Term{}
|
||||
|
||||
%{
|
||||
title: item.name,
|
||||
slug: item.slug,
|
||||
resource: "category",
|
||||
}
|
||||
else
|
||||
item = nav_to_post_map["post/#{meta_map["_menu_item_object_id"]}"] || %Post{}
|
||||
|
||||
%{
|
||||
title: item.post_title,
|
||||
slug: item.post_name,
|
||||
resource: "posts",
|
||||
}
|
||||
end
|
||||
|
||||
%{
|
||||
post_id: post."ID",
|
||||
type: meta_map["_menu_item_object"],
|
||||
target_id: meta_map["_menu_item_object_id"],
|
||||
parent_id: meta_map["_menu_item_menu_item_parent"],
|
||||
url: meta_map["_menu_item_url"],
|
||||
related_item: related_item,
|
||||
children: arrange_menu_item_posts(nav_posts, Integer.to_string(post."ID"), nav_to_post_map),
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
defp make_nav_to_post_map(nav_posts) do
|
||||
nav_post_meta_map = nav_posts |> Post.metas_map()
|
||||
|
||||
linked_post_ids =
|
||||
nav_post_meta_map
|
||||
|> Enum.filter(fn {_key, value} ->
|
||||
value["_menu_item_object"] != "category"
|
||||
end)
|
||||
|> Enum.map(fn {_key, value} ->
|
||||
value["_menu_item_object_id"]
|
||||
end)
|
||||
|
||||
nav_to_post_map =
|
||||
Post
|
||||
|> where([p], p."ID" in ^linked_post_ids)
|
||||
|> Repo.all()
|
||||
|> Enum.map(fn post ->
|
||||
{"post/#{post."ID"}", post}
|
||||
end)
|
||||
|> Map.new
|
||||
|
||||
linked_category_ids =
|
||||
nav_post_meta_map
|
||||
|> Enum.filter(fn {_key, value} ->
|
||||
value["_menu_item_object"] == "category"
|
||||
end)
|
||||
|> Enum.map(fn {_key, value} ->
|
||||
value["_menu_item_object_id"]
|
||||
end)
|
||||
|
||||
nav_to_category_map =
|
||||
Term
|
||||
|> where([t], t.term_id in ^linked_category_ids)
|
||||
|> Repo.all()
|
||||
|> Enum.map(fn category ->
|
||||
{"category/#{category.term_id}", category}
|
||||
end)
|
||||
|> Map.new
|
||||
|
||||
nav_to_post_map |> Map.merge(nav_to_category_map)
|
||||
end
|
||||
end
|
31
apps/content/lib/option.ex
Normal file
31
apps/content/lib/option.ex
Normal file
|
@ -0,0 +1,31 @@
|
|||
defmodule Content.Option do
|
||||
@moduledoc """
|
||||
A configuration option for the site.
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:option_id, :id, autogenerate: true}
|
||||
schema "wp_options" do
|
||||
field :option_name, :string
|
||||
field :autoload, :string
|
||||
field :option_value, :string
|
||||
end
|
||||
|
||||
def changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:option_id, :option_name, :option_value, :autoload])
|
||||
end
|
||||
|
||||
def parse_option_value(struct) do
|
||||
case PhpSerializer.unserialize(struct.option_value) do
|
||||
{:ok, values} ->
|
||||
values
|
||||
end
|
||||
end
|
||||
|
||||
def put_new_value(struct, value) do
|
||||
struct
|
||||
|> change(%{option_value: PhpSerializer.serialize(value)})
|
||||
end
|
||||
end
|
29
apps/content/lib/options.ex
Normal file
29
apps/content/lib/options.ex
Normal file
|
@ -0,0 +1,29 @@
|
|||
defmodule Content.Options do
|
||||
@moduledoc """
|
||||
Query the option key-value pairs for the site.
|
||||
"""
|
||||
alias Content.Option
|
||||
alias Content.Repo
|
||||
|
||||
def get(key), do: Option |> Repo.get_by(option_name: key)
|
||||
|
||||
def get_value(key) do
|
||||
case get(key) do
|
||||
nil ->
|
||||
nil
|
||||
opt ->
|
||||
opt
|
||||
|> (&(&1.option_value)).()
|
||||
end
|
||||
end
|
||||
|
||||
def get_value_as_int(key) do
|
||||
case get_value(key) do
|
||||
nil ->
|
||||
{nil, nil}
|
||||
opt ->
|
||||
opt
|
||||
|> Integer.parse()
|
||||
end
|
||||
end
|
||||
end
|
136
apps/content/lib/post.ex
Normal file
136
apps/content/lib/post.ex
Normal file
|
@ -0,0 +1,136 @@
|
|||
defmodule Content.Post do
|
||||
@moduledoc """
|
||||
One "post" i.e. a blog post, page, attachment, or item of a custom post type.
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Content.Slugs
|
||||
|
||||
@primary_key {:ID, :id, autogenerate: true}
|
||||
@derive {Phoenix.Param, key: :post_name}
|
||||
schema "wp_posts" do
|
||||
field :post_date, :naive_datetime
|
||||
field :post_date_gmt, :naive_datetime
|
||||
field :post_content, :string, default: ""
|
||||
field :post_title, :string
|
||||
field :post_excerpt, :string
|
||||
field :post_status, :string
|
||||
field :comment_status, :string
|
||||
field :ping_status, :string
|
||||
field :post_password, :string, default: ""
|
||||
field :post_name, :string
|
||||
field :to_ping, :string, default: ""
|
||||
field :pinged, :string, default: ""
|
||||
field :post_modified, :naive_datetime
|
||||
field :post_modified_gmt, :naive_datetime
|
||||
field :post_content_filtered, :string, default: ""
|
||||
field :post_parent, :integer
|
||||
field :guid, :string
|
||||
field :menu_order, :integer
|
||||
field :post_type, :string
|
||||
field :post_mime_type, :string
|
||||
field :comment_count, :integer
|
||||
field :sticky, :boolean, [virtual: true, default: false]
|
||||
has_many :metas, Content.Postmeta, foreign_key: :post_id
|
||||
has_many :comments, Content.Comment, foreign_key: :comment_post_ID
|
||||
has_many :term_relationships, Content.TermRelationship, foreign_key: :object_id
|
||||
has_many :categories, through: [:term_relationships, :category, :term]
|
||||
has_many :tags, through: [:term_relationships, :tag, :term]
|
||||
has_one :post_format, through: [:term_relationships, :post_format, :term]
|
||||
belongs_to :author, Content.User, foreign_key: :post_author, references: :ID
|
||||
end
|
||||
|
||||
def changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(
|
||||
params,
|
||||
[
|
||||
:ID,
|
||||
:post_author,
|
||||
:post_date,
|
||||
:post_date_gmt,
|
||||
:post_content,
|
||||
:post_title,
|
||||
:post_excerpt,
|
||||
:post_status,
|
||||
:comment_status,
|
||||
:ping_status,
|
||||
:post_password,
|
||||
:post_name,
|
||||
:to_ping,
|
||||
:pinged,
|
||||
:post_content_filtered,
|
||||
:post_parent,
|
||||
:menu_order,
|
||||
:post_type,
|
||||
:post_mime_type,
|
||||
:comment_count,
|
||||
:sticky,
|
||||
])
|
||||
|> put_default(:post_date, NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second))
|
||||
|> put_default(:post_date_gmt, NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second))
|
||||
|> put_change(:post_modified, NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second))
|
||||
|> put_change(:post_modified_gmt, NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second))
|
||||
|> Slugs.ensure_post_has_slug()
|
||||
|> maybe_put_guid()
|
||||
|> validate_required([:post_name, :post_status])
|
||||
|> validate_inclusion(:post_status, ["publish", "future", "draft", "pending", "private", "trash", "auto-draft", "inherit"])
|
||||
end
|
||||
|
||||
def put_default(changeset, key, value) do
|
||||
if is_nil(changeset |> get_field(key)) do
|
||||
changeset
|
||||
|> put_change(key, value)
|
||||
else
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
def before_more(string) do
|
||||
string
|
||||
|> String.split("<!--more-->")
|
||||
|> Enum.at(0)
|
||||
end
|
||||
|
||||
def content_page(struct, page) do
|
||||
(struct.post_content || "")
|
||||
|> String.split("<!--nextpage-->")
|
||||
|> Enum.at(page - 1)
|
||||
|> Kernel.||("")
|
||||
end
|
||||
|
||||
def content_page_count(struct) do
|
||||
(struct.post_content || "")
|
||||
|> String.split("<!--nextpage-->")
|
||||
|> Enum.count
|
||||
end
|
||||
|
||||
def paginated_post?(struct) do
|
||||
content_page_count(struct) > 1
|
||||
end
|
||||
|
||||
def metas_map(list) when is_list(list) do
|
||||
list
|
||||
|> Enum.map(fn post ->
|
||||
{post."ID", metas_map(post)}
|
||||
end)
|
||||
|> Map.new
|
||||
end
|
||||
def metas_map(%Content.Post{} = struct) do
|
||||
struct.metas
|
||||
|> Enum.map(&({&1.meta_key, &1.meta_value}))
|
||||
|> Map.new
|
||||
end
|
||||
|
||||
def maybe_put_guid(changeset) do
|
||||
import ContentWeb.Router.Helpers, only: [posts_url: 3]
|
||||
slug = changeset |> get_field(:post_name)
|
||||
|
||||
case slug do
|
||||
nil -> changeset
|
||||
_ ->
|
||||
changeset
|
||||
|> put_default(:guid, posts_url(ContentWeb.Endpoint, :show, slug))
|
||||
end
|
||||
end
|
||||
end
|
19
apps/content/lib/postmeta.ex
Normal file
19
apps/content/lib/postmeta.ex
Normal file
|
@ -0,0 +1,19 @@
|
|||
defmodule Content.Postmeta do
|
||||
@moduledoc """
|
||||
An item of metadata about a post.
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:meta_id, :id, autogenerate: true}
|
||||
schema "wp_postmeta" do
|
||||
belongs_to :post, Content.Post, references: :ID
|
||||
field :meta_key, :string
|
||||
field :meta_value, :string
|
||||
end
|
||||
|
||||
def changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:meta_id, :post_id, :meta_key, :meta_value])
|
||||
end
|
||||
end
|
286
apps/content/lib/posts.ex
Normal file
286
apps/content/lib/posts.ex
Normal file
|
@ -0,0 +1,286 @@
|
|||
defmodule Content.Posts do
|
||||
@page_size 3
|
||||
|
||||
@moduledoc """
|
||||
The Content context.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Content.Repo
|
||||
|
||||
alias Content.Option
|
||||
alias Content.Post
|
||||
alias Ecto.Changeset
|
||||
|
||||
@preloads [:metas, :author, :categories, :tags, :comments, :post_format]
|
||||
|
||||
@doc """
|
||||
Returns the lisdpt of posts for admin interface.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_admin_posts()
|
||||
[%Post{}, ...]
|
||||
|
||||
"""
|
||||
def list_admin_posts(page, post_type \\ "post") do
|
||||
post_type = post_type || "post"
|
||||
Repo.all(
|
||||
from p in Post,
|
||||
where: p.post_type == ^post_type,
|
||||
where: p.post_status in ["publish", "future", "draft", "pending", "private", "inherit"],
|
||||
preload: ^@preloads,
|
||||
order_by: [desc: p.post_date],
|
||||
limit: @page_size,
|
||||
offset: ^(@page_size * (String.to_integer(page) - 1))
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of posts.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_posts()
|
||||
[%Post{}, ...]
|
||||
|
||||
"""
|
||||
def list_posts(params \\ %{}) do
|
||||
page = params |> Map.get("page", "1")
|
||||
|
||||
normal_posts =
|
||||
params
|
||||
|> post_scope_for_params()
|
||||
|> limit(@page_size)
|
||||
|> offset(^(@page_size * (String.to_integer(page) - 1)))
|
||||
|> Repo.all()
|
||||
|
||||
sticky_posts_for_page(params) ++ normal_posts
|
||||
end
|
||||
|
||||
def post_scope_for_params(params) do
|
||||
post_type = params |> Map.get("post_type", "post")
|
||||
category = params |> Map.get("category")
|
||||
|
||||
query =
|
||||
post_scope()
|
||||
|> where([p], p.post_type == ^post_type)
|
||||
|
||||
if category do
|
||||
query |> join(:inner, [p], term in assoc(p, :categories), on: term.slug == ^category)
|
||||
else
|
||||
query
|
||||
end
|
||||
end
|
||||
|
||||
def post_scope do
|
||||
from p in Post,
|
||||
where: p.post_status == "publish",
|
||||
preload: ^@preloads,
|
||||
order_by: [desc: p.post_date]
|
||||
end
|
||||
|
||||
def post_scope_with_drafts do
|
||||
from p in Post,
|
||||
preload: ^@preloads,
|
||||
order_by: [desc: p.post_date]
|
||||
end
|
||||
|
||||
def sticky_posts_for_page(%{"page" => "1"} = params) do
|
||||
sticky_posts =
|
||||
params
|
||||
|> post_scope_for_params()
|
||||
|> where([p], p.'ID' in ^sticky_ids())
|
||||
|> Repo.all()
|
||||
|
||||
sticky_posts
|
||||
|> Enum.map(fn post ->
|
||||
post
|
||||
|> Changeset.change(%{sticky: true})
|
||||
|> Changeset.apply_changes()
|
||||
end)
|
||||
end
|
||||
def sticky_posts_for_page(_), do: []
|
||||
|
||||
defp sticky_ids do
|
||||
case Repo.one(from opt in Option, where: opt.option_name == "sticky_posts") do
|
||||
nil ->
|
||||
[]
|
||||
option ->
|
||||
option
|
||||
|> Option.parse_option_value
|
||||
|> Enum.map(&(elem(&1, 1)))
|
||||
end
|
||||
end
|
||||
|
||||
def last_page(params \\ %{}) do
|
||||
post_count =
|
||||
params
|
||||
|> post_scope_for_params()
|
||||
|> Repo.aggregate(:count, :ID)
|
||||
|
||||
post_count
|
||||
|> (&(&1 / @page_size)).()
|
||||
|> Float.ceil
|
||||
|> trunc
|
||||
end
|
||||
|
||||
def thumbs_for_posts(posts) do
|
||||
post_to_thumbnail_id =
|
||||
posts
|
||||
|> Enum.map(fn post -> {post.'ID', (post |> Post.metas_map)["_thumbnail_id"]} end)
|
||||
|> Enum.reject(&(elem(&1, 1) == nil))
|
||||
|
||||
thumbs =
|
||||
Post
|
||||
|> preload(:metas)
|
||||
|> where([thumb], thumb.'ID' in ^Enum.map(post_to_thumbnail_id, &(elem(&1, 1))))
|
||||
|> Repo.all()
|
||||
|> Enum.map(fn thumb -> {thumb.'ID', thumb} end)
|
||||
|> Map.new
|
||||
|
||||
post_to_thumbnail_id
|
||||
|> Enum.map(fn {key, value} -> {key, thumbs[String.to_integer(value)]} end)
|
||||
|> Map.new
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single posts.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Wp posts does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_posts!(123)
|
||||
%Post{}
|
||||
|
||||
iex> get_posts!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_posts!(slug) do
|
||||
id_filter = fn scope, id ->
|
||||
|
||||
case Integer.parse(id, 10) do
|
||||
:error ->
|
||||
scope |> where([p], p.post_name == ^id)
|
||||
{int_id, _} ->
|
||||
scope |> where([p], p.'ID' == ^int_id)
|
||||
end
|
||||
end
|
||||
|
||||
post_scope()
|
||||
|> where([p], p.post_type != "nav_menu_item")
|
||||
|> id_filter.(slug)
|
||||
|> Repo.one!()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single post that may or may not be in draft status.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Wp posts does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_post_with_drafts!(123)
|
||||
%Post{}
|
||||
|
||||
iex> get_post_with_drafts!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_post_with_drafts!(slug) do
|
||||
id_filter = fn scope, id ->
|
||||
|
||||
case Integer.parse(id, 10) do
|
||||
:error ->
|
||||
scope |> where([p], p.post_name == ^id)
|
||||
{int_id, ""} ->
|
||||
scope |> where([p], p.'ID' == ^int_id)
|
||||
{_int_id, _} ->
|
||||
scope |> where([p], p.post_name == ^id)
|
||||
end
|
||||
end
|
||||
|
||||
post_scope_with_drafts()
|
||||
|> where([p], p.post_type != "nav_menu_item")
|
||||
|> id_filter.(slug)
|
||||
|> Repo.one!()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a posts.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_posts(%{field: value})
|
||||
{:ok, %Post{}}
|
||||
|
||||
iex> create_posts(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_posts(attrs \\ %{}) do
|
||||
%Post{}
|
||||
|> Post.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Builds a post for preview, but does not save it.
|
||||
"""
|
||||
def preview_post(attrs \\ %{}) do
|
||||
%Post{}
|
||||
|> Repo.preload(@preloads)
|
||||
|> Post.changeset(attrs)
|
||||
|> Changeset.put_change(:post_name, "preview")
|
||||
|> Changeset.apply_changes()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a posts.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_posts(posts, %{field: new_value})
|
||||
{:ok, %Post{}}
|
||||
|
||||
iex> update_posts(posts, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_posts(%Post{} = posts, attrs) do
|
||||
posts
|
||||
|> Post.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a Post.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_posts(posts)
|
||||
{:ok, %Post{}}
|
||||
|
||||
iex> delete_posts(posts)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_posts(%Post{} = posts) do
|
||||
Repo.delete(posts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking posts changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_posts(posts)
|
||||
%Ecto.Changeset{source: %Post{}}
|
||||
|
||||
"""
|
||||
def change_posts(%Post{} = posts) do
|
||||
Post.changeset(posts, %{})
|
||||
end
|
||||
end
|
5
apps/content/lib/repo.ex
Normal file
5
apps/content/lib/repo.ex
Normal file
|
@ -0,0 +1,5 @@
|
|||
defmodule Content.Repo do
|
||||
use Ecto.Repo,
|
||||
otp_app: :content,
|
||||
adapter: Ecto.Adapters.Postgres
|
||||
end
|
9
apps/content/lib/scheduler.ex
Normal file
9
apps/content/lib/scheduler.ex
Normal file
|
@ -0,0 +1,9 @@
|
|||
defmodule Content.Scheduler do
|
||||
@moduledoc """
|
||||
The Quantum cron-like scheduler for this application. See config.exs for
|
||||
configured jobs.
|
||||
"""
|
||||
|
||||
use Quantum.Scheduler,
|
||||
otp_app: :content
|
||||
end
|
97
apps/content/lib/shortcodes.ex
Normal file
97
apps/content/lib/shortcodes.ex
Normal file
|
@ -0,0 +1,97 @@
|
|||
defmodule Content.ShortcodeParser do
|
||||
use Neotomex.ExGrammar
|
||||
|
||||
@root true
|
||||
define :document, "(comment / shortcode / notcode)*" do
|
||||
tail ->
|
||||
tail |> Enum.join
|
||||
end
|
||||
|
||||
define :notcode, "not_open_bracket+" do
|
||||
content ->
|
||||
content |> Enum.join
|
||||
end
|
||||
|
||||
define :comment, "<open_bracket> <open_bracket> ([^\\]]+ close_bracket?)+ <close_bracket>" do
|
||||
[inner] ->
|
||||
inner
|
||||
|> Enum.map(fn [chars, nil] -> "#{chars |> Enum.join}]" end)
|
||||
|> Enum.join
|
||||
|> (&("[#{&1}")).()
|
||||
end
|
||||
|
||||
define :shortcode, "<open_bracket> <spaces?> name <spaces?> attribute* <close_bracket> <!'('> (notcode? <open_bracket> <'/'> <spaces?> name <spaces?> <close_bracket>)?" do
|
||||
[name, attributes, nil] ->
|
||||
Content.Shortcodes.dispatch(name, attributes)
|
||||
[name, attributes, [content, closing_name]] when closing_name == name ->
|
||||
Content.Shortcodes.dispatch(name, attributes, content || "")
|
||||
end
|
||||
|
||||
define :attribute, "name <spaces?>"
|
||||
define :name, "(namechar+)", do: (chars -> Enum.join(chars))
|
||||
define :namechar, "[A-Za-z0-9] / dash / underscore"
|
||||
define :dash, "<'-'>"
|
||||
define :underscore, "<'_'>"
|
||||
define :open_bracket, "<'['>", do: ["["]
|
||||
define :not_open_bracket, "[^\\[]"
|
||||
define :close_bracket, "<']'>", do: ["]"]
|
||||
define :close_comment, "close_bracket close_bracket"
|
||||
define :spaces, "[\s\\r\\n]*"
|
||||
|
||||
@_neotomex_definitions Map.put(@_neotomex_definitions,
|
||||
:not_open_bracket,
|
||||
{{:terminal, ~r/^[^[]/u}, nil})
|
||||
end
|
||||
|
||||
defmodule Content.Shortcodes do
|
||||
@moduledoc """
|
||||
For handling wordpress style shortcodes in strings.
|
||||
"""
|
||||
def expand_shortcodes(frag) do
|
||||
{:ok, tree} = Floki.parse_fragment(frag)
|
||||
|
||||
case tree do
|
||||
[text] when is_binary(text) ->
|
||||
{:ok, result} = processed_text(text) |> Floki.parse_fragment
|
||||
result
|
||||
_ ->
|
||||
tree
|
||||
|> Floki.traverse_and_update(fn
|
||||
tag ->
|
||||
tag |> transform_text_nodes
|
||||
end)
|
||||
end
|
||||
|> Floki.raw_html(encode: false)
|
||||
end
|
||||
|
||||
defp transform_text_nodes({tag_name, attrs, children}) do
|
||||
new_children =
|
||||
children
|
||||
|> Enum.map(fn
|
||||
text when is_binary(text) ->
|
||||
{:ok, [result]} = processed_text(text) |> Floki.parse_fragment
|
||||
result
|
||||
other -> other
|
||||
end)
|
||||
{tag_name, attrs, new_children}
|
||||
end
|
||||
|
||||
defp processed_text(text) do
|
||||
text =
|
||||
text
|
||||
|> String.replace("\r", "")
|
||||
|
||||
case Content.ShortcodeParser.parse(text) do
|
||||
{:ok, result, remainder} ->
|
||||
[result, remainder] |> Enum.join
|
||||
{:ok, result} ->
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch(tag, _attrs), do: String.upcase(String.reverse(tag))
|
||||
|
||||
def dispatch(_tag, _attrs, content) do
|
||||
String.upcase(String.reverse(content))
|
||||
end
|
||||
end
|
68
apps/content/lib/slugs.ex
Normal file
68
apps/content/lib/slugs.ex
Normal file
|
@ -0,0 +1,68 @@
|
|||
defmodule Content.Slugs do
|
||||
@moduledoc """
|
||||
Provides functions for working with post slugs and ensuring that they are unique.
|
||||
"""
|
||||
import Ecto.{Changeset, Query}
|
||||
alias Content.{Post, Repo}
|
||||
|
||||
def ensure_post_has_slug(changeset) do
|
||||
cond do
|
||||
!is_nil(changeset |> get_field(:post_name)) ->
|
||||
changeset
|
||||
is_nil(changeset |> get_field(:post_title)) ->
|
||||
changeset
|
||||
|> put_change(
|
||||
:post_name,
|
||||
changeset
|
||||
|> get_field(:post_date)
|
||||
|> Kernel.||(NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second))
|
||||
|> Timex.format!("%F", :strftime)
|
||||
|> Slugger.slugify_downcase()
|
||||
|> unique_slug(changeset |> get_field(:ID))
|
||||
)
|
||||
true ->
|
||||
changeset
|
||||
|> put_change(
|
||||
:post_name,
|
||||
changeset
|
||||
|> get_field(:post_title)
|
||||
|> Slugger.slugify_downcase()
|
||||
|> unique_slug(changeset |> get_field(:ID))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp unique_slug(proposed_slug, post_id, postfix_number \\ 0) do
|
||||
proposed_slug_with_postfix =
|
||||
if postfix_number == 0 do
|
||||
proposed_slug
|
||||
else
|
||||
"#{proposed_slug}-#{postfix_number}"
|
||||
end
|
||||
|
||||
competition_count =
|
||||
Repo.aggregate(
|
||||
(
|
||||
Post
|
||||
|> where([post], post.post_name == ^proposed_slug_with_postfix)
|
||||
|> post_id_match(post_id)
|
||||
),
|
||||
:count,
|
||||
:ID
|
||||
)
|
||||
|
||||
if competition_count == 0 do
|
||||
proposed_slug_with_postfix
|
||||
else
|
||||
unique_slug(proposed_slug, post_id, postfix_number + 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp post_id_match(query, nil) do
|
||||
query
|
||||
end
|
||||
|
||||
defp post_id_match(query, id) when is_number(id) do
|
||||
from p in query, where: p.'ID' != ^id
|
||||
end
|
||||
end
|
19
apps/content/lib/term.ex
Normal file
19
apps/content/lib/term.ex
Normal file
|
@ -0,0 +1,19 @@
|
|||
defmodule Content.Term do
|
||||
@moduledoc """
|
||||
Represents one 'term', i.e. a grouping under a taxonomy.
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:term_id, :id, autogenerate: true}
|
||||
schema "wp_terms" do
|
||||
field :name, :string
|
||||
field :slug, :string
|
||||
field :term_group, :integer
|
||||
end
|
||||
|
||||
def changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:term_id, :name, :slug, :term_group])
|
||||
end
|
||||
end
|
43
apps/content/lib/term_relationship.ex
Normal file
43
apps/content/lib/term_relationship.ex
Normal file
|
@ -0,0 +1,43 @@
|
|||
defmodule Content.TermRelationship do
|
||||
@moduledoc """
|
||||
Maintains the relationship between a term_taxonomy and a post / page / or object.
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Content.{Post}
|
||||
|
||||
@primary_key {:object_id, :integer, []}
|
||||
@primary_key {:term_taxonomy_id, :integer, []}
|
||||
schema "wp_term_relationships" do
|
||||
field :term_order, :integer
|
||||
belongs_to :post, Post, foreign_key: :object_id, references: :ID
|
||||
belongs_to :term_taxonomy,
|
||||
Content.TermTaxonomy,
|
||||
foreign_key: :term_taxonomy_id,
|
||||
references: :term_taxonomy_id,
|
||||
define_field: false
|
||||
belongs_to :category,
|
||||
Content.TermTaxonomy,
|
||||
foreign_key: :term_taxonomy_id,
|
||||
references: :term_taxonomy_id,
|
||||
define_field: false,
|
||||
where: [taxonomy: "category"]
|
||||
belongs_to :tag,
|
||||
Content.TermTaxonomy,
|
||||
foreign_key: :term_taxonomy_id,
|
||||
references: :term_taxonomy_id,
|
||||
define_field: false,
|
||||
where: [taxonomy: "post_tag"]
|
||||
belongs_to :post_format,
|
||||
Content.TermTaxonomy,
|
||||
foreign_key: :term_taxonomy_id,
|
||||
references: :term_taxonomy_id,
|
||||
define_field: false,
|
||||
where: [taxonomy: "post_format"]
|
||||
end
|
||||
|
||||
def changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:object_id, :term_taxonomy_id, :term_order])
|
||||
end
|
||||
end
|
21
apps/content/lib/term_taxonomy.ex
Normal file
21
apps/content/lib/term_taxonomy.ex
Normal file
|
@ -0,0 +1,21 @@
|
|||
defmodule Content.TermTaxonomy do
|
||||
@moduledoc """
|
||||
A record in a taxonomy which organizes terms and posts in the system.
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:term_taxonomy_id, :id, autogenerate: true}
|
||||
schema "wp_term_taxonomy" do
|
||||
field :taxonomy, :string
|
||||
field :description, :string
|
||||
field :parent, :integer
|
||||
field :count, :integer
|
||||
belongs_to :term, Content.Term, foreign_key: :term_id, references: :term_id
|
||||
end
|
||||
|
||||
def changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:term_taxonomy_id, :term_id, :taxonomy, :description, :parent, :count])
|
||||
end
|
||||
end
|
19
apps/content/lib/termmeta.ex
Normal file
19
apps/content/lib/termmeta.ex
Normal file
|
@ -0,0 +1,19 @@
|
|||
defmodule Content.Termmeta do
|
||||
@moduledoc """
|
||||
Represents one piece of metadata around one "term" (a grouping under a taxonomy).
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:meta_id, :id, autogenerate: true}
|
||||
schema "wp_termmeta" do
|
||||
field :term_id, :integer
|
||||
field :meta_key, :string
|
||||
field :meta_value, :string
|
||||
end
|
||||
|
||||
def changeset(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:meta_id, :term_id, :meta_key, :meta_value])
|
||||
end
|
||||
end
|
15
apps/content/lib/terms.ex
Normal file
15
apps/content/lib/terms.ex
Normal file
|
@ -0,0 +1,15 @@
|
|||
defmodule Content.Terms do
|
||||
@moduledoc """
|
||||
This module contains functions for retrieving, manipulating, and saving
|
||||
Terms.
|
||||
"""
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
def categories do
|
||||
from t in Content.Term,
|
||||
join: tt in Content.TermTaxonomy,
|
||||
on: t.term_id == tt.term_id,
|
||||
where: tt.taxonomy == "category"
|
||||
end
|
||||
end
|
84
apps/content/mix.exs
Normal file
84
apps/content/mix.exs
Normal file
|
@ -0,0 +1,84 @@
|
|||
defmodule Content.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :content,
|
||||
version: "0.0.1",
|
||||
build_path: "../../_build",
|
||||
config_path: "../../config/config.exs",
|
||||
deps_path: "../../deps",
|
||||
lockfile: "../../mix.lock",
|
||||
elixir: "~> 1.8",
|
||||
elixirc_paths: elixirc_paths(Mix.env),
|
||||
compilers: [:phoenix, :gettext] ++ Mix.compilers,
|
||||
start_permanent: Mix.env == :prod,
|
||||
aliases: aliases(),
|
||||
deps: deps(),
|
||||
test_coverage: [tool: ExCoveralls],
|
||||
preferred_cli_env: [coveralls: :test, "coveralls.detail": :test, "coveralls.post": :test, "coveralls.html": :test]
|
||||
]
|
||||
end
|
||||
|
||||
# Configuration for the OTP application.
|
||||
#
|
||||
# Type `mix help compile.app` for more information.
|
||||
def application do
|
||||
[
|
||||
mod: {Content.Application, []},
|
||||
extra_applications: [:logger, :runtime_tools, :sitemap]
|
||||
]
|
||||
end
|
||||
|
||||
# Specifies which paths to compile per environment.
|
||||
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||
defp elixirc_paths(_), do: ["lib"]
|
||||
|
||||
# Specifies your project dependencies.
|
||||
#
|
||||
# Type `mix help deps` for examples and options.
|
||||
defp deps do
|
||||
[
|
||||
{:content_web, in_umbrella: true},
|
||||
{:phoenix, "~> 1.5.3"},
|
||||
{:phoenix_pubsub, "~> 2.0"},
|
||||
{:ecto_sql, "~> 3.4"},
|
||||
{:postgrex, "~> 0.15.0"},
|
||||
{:plug_cowboy, "~> 2.0"},
|
||||
{:phoenix_ecto, "~> 4.0"},
|
||||
{:phoenix_html, "~> 2.10"},
|
||||
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
||||
{:gettext, "~> 0.11"},
|
||||
{:cowboy, "~> 2.7"},
|
||||
{:php_serializer, "~> 0.9.0"},
|
||||
{:quantum, "~> 2.3"},
|
||||
{:timex, "~> 3.1"},
|
||||
{:excoveralls, "~> 0.10", only: [:dev, :test]},
|
||||
{:phoenix_html_sanitizer, "~> 1.0.0"},
|
||||
{:bcrypt_elixir, "~> 1.0"},
|
||||
{:comeonin, "~> 4.0"},
|
||||
{:earmark, "~> 1.4.2" },
|
||||
{:slugger, "~> 0.3"},
|
||||
{:ecto, "~> 3.4.3"},
|
||||
{:floki, "~> 0.25.0"},
|
||||
{:mock, "~> 0.3.0", only: :test},
|
||||
{:meck, "~> 0.8.13", only: :test},
|
||||
{:sitemap, "~> 1.1"},
|
||||
{:neotomex, "~> 0.1.7"},
|
||||
]
|
||||
end
|
||||
|
||||
# Aliases are shortcuts or tasks specific to the current project.
|
||||
# For example, to create, migrate and run the seeds file at once:
|
||||
#
|
||||
# $ mix ecto.setup
|
||||
#
|
||||
# See the documentation for `Mix` for more info on aliases.
|
||||
defp aliases do
|
||||
[
|
||||
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
|
||||
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
||||
test: ["ecto.create --quiet", "ecto.migrate", "test"]
|
||||
]
|
||||
end
|
||||
end
|
97
apps/content/priv/gettext/en/LC_MESSAGES/errors.po
Normal file
97
apps/content/priv/gettext/en/LC_MESSAGES/errors.po
Normal file
|
@ -0,0 +1,97 @@
|
|||
## `msgid`s in this file come from POT (.pot) files.
|
||||
##
|
||||
## Do not add, change, or remove `msgid`s manually here as
|
||||
## they're tied to the ones in the corresponding POT file
|
||||
## (with the same domain).
|
||||
##
|
||||
## Use `mix gettext.extract --merge` or `mix gettext.merge`
|
||||
## to merge POT files into PO files.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Language: en\n"
|
||||
|
||||
## From Ecto.Changeset.cast/4
|
||||
msgid "can't be blank"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.unique_constraint/3
|
||||
msgid "has already been taken"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.put_change/3
|
||||
msgid "is invalid"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_acceptance/3
|
||||
msgid "must be accepted"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_format/3
|
||||
msgid "has invalid format"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_subset/3
|
||||
msgid "has an invalid entry"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_exclusion/3
|
||||
msgid "is reserved"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_confirmation/3
|
||||
msgid "does not match confirmation"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.no_assoc_constraint/3
|
||||
msgid "is still associated with this entry"
|
||||
msgstr ""
|
||||
|
||||
msgid "are still associated with this entry"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_length/3
|
||||
msgid "should be %{count} character(s)"
|
||||
msgid_plural "should be %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should have %{count} item(s)"
|
||||
msgid_plural "should have %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should be at least %{count} character(s)"
|
||||
msgid_plural "should be at least %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should have at least %{count} item(s)"
|
||||
msgid_plural "should have at least %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should be at most %{count} character(s)"
|
||||
msgid_plural "should be at most %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should have at most %{count} item(s)"
|
||||
msgid_plural "should have at most %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
## From Ecto.Changeset.validate_number/3
|
||||
msgid "must be less than %{number}"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be greater than %{number}"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be less than or equal to %{number}"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be greater than or equal to %{number}"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be equal to %{number}"
|
||||
msgstr ""
|
95
apps/content/priv/gettext/errors.pot
Normal file
95
apps/content/priv/gettext/errors.pot
Normal file
|
@ -0,0 +1,95 @@
|
|||
## This file is a PO Template file.
|
||||
##
|
||||
## `msgid`s here are often extracted from source code.
|
||||
## Add new translations manually only if they're dynamic
|
||||
## translations that can't be statically extracted.
|
||||
##
|
||||
## Run `mix gettext.extract` to bring this file up to
|
||||
## date. Leave `msgstr`s empty as changing them here as no
|
||||
## effect: edit them in PO (`.po`) files instead.
|
||||
|
||||
## From Ecto.Changeset.cast/4
|
||||
msgid "can't be blank"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.unique_constraint/3
|
||||
msgid "has already been taken"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.put_change/3
|
||||
msgid "is invalid"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_acceptance/3
|
||||
msgid "must be accepted"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_format/3
|
||||
msgid "has invalid format"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_subset/3
|
||||
msgid "has an invalid entry"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_exclusion/3
|
||||
msgid "is reserved"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_confirmation/3
|
||||
msgid "does not match confirmation"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.no_assoc_constraint/3
|
||||
msgid "is still associated with this entry"
|
||||
msgstr ""
|
||||
|
||||
msgid "are still associated with this entry"
|
||||
msgstr ""
|
||||
|
||||
## From Ecto.Changeset.validate_length/3
|
||||
msgid "should be %{count} character(s)"
|
||||
msgid_plural "should be %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should have %{count} item(s)"
|
||||
msgid_plural "should have %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should be at least %{count} character(s)"
|
||||
msgid_plural "should be at least %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should have at least %{count} item(s)"
|
||||
msgid_plural "should have at least %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should be at most %{count} character(s)"
|
||||
msgid_plural "should be at most %{count} character(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "should have at most %{count} item(s)"
|
||||
msgid_plural "should have at most %{count} item(s)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
## From Ecto.Changeset.validate_number/3
|
||||
msgid "must be less than %{number}"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be greater than %{number}"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be less than or equal to %{number}"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be greater than or equal to %{number}"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be equal to %{number}"
|
||||
msgstr ""
|
|
@ -0,0 +1,133 @@
|
|||
defmodule Content.Repo.Migrations.CreateWpSchema do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table("wp_commentmeta", primary_key: false) do
|
||||
add :meta_id, :serial, primary_key: true
|
||||
add :comment_id, :integer
|
||||
add :meta_key, :text
|
||||
end
|
||||
|
||||
create table("wp_comments", primary_key: false) do
|
||||
add :comment_ID, :serial, primary_key: true
|
||||
add :comment_post_ID, :integer
|
||||
add :comment_author, :text
|
||||
add :comment_author_email, :text
|
||||
add :comment_author_url, :text
|
||||
add :comment_author_IP, :text
|
||||
add :comment_date, :naive_datetime
|
||||
add :comment_date_gmt, :naive_datetime
|
||||
add :comment_content, :string
|
||||
add :comment_karma, :integer
|
||||
add :comment_approved, :text
|
||||
add :comment_agent, :text
|
||||
add :comment_type, :text
|
||||
add :comment_parent, :integer
|
||||
add :user_id, :integer
|
||||
end
|
||||
|
||||
create table("wp_links", primary_key: false) do
|
||||
add :link_id, :serial, primary_key: true
|
||||
add :link_url, :text
|
||||
add :link_name, :text
|
||||
add :link_image, :text
|
||||
add :link_target, :text
|
||||
add :link_description, :text
|
||||
add :link_visible, :text
|
||||
add :link_owner, :integer
|
||||
add :link_rating, :integer
|
||||
add :link_updated, :naive_datetime
|
||||
add :link_rel, :text
|
||||
add :link_rss, :text
|
||||
end
|
||||
|
||||
create table("wp_options", primary_key: false) do
|
||||
add :option_id, :serial, primary_key: true
|
||||
add :option_name, :text
|
||||
add :autoload, :text
|
||||
add :option_value, :text
|
||||
end
|
||||
|
||||
create table("wp_postmeta", primary_key: false) do
|
||||
add :meta_id, :serial, primary_key: true
|
||||
add :post_id, :integer
|
||||
add :meta_key, :text
|
||||
add :meta_value, :text
|
||||
end
|
||||
|
||||
create table("wp_posts", primary_key: false) do
|
||||
add :ID, :integer, [:primary_key]
|
||||
add :post_author, :integer
|
||||
add :post_date, :naive_datetime
|
||||
add :post_date_gmt, :naive_datetime
|
||||
add :post_content, :text
|
||||
add :post_title, :string
|
||||
add :post_excerpt, :string
|
||||
add :post_status, :text
|
||||
add :comment_status, :text
|
||||
add :ping_status, :text
|
||||
add :post_password, :text
|
||||
add :post_name, :text
|
||||
add :to_ping, :string
|
||||
add :pinged, :string
|
||||
add :post_modified, :naive_datetime
|
||||
add :post_modified_gmt, :naive_datetime
|
||||
add :post_content_filtered, :text
|
||||
add :post_parent, :integer
|
||||
add :guid, :text
|
||||
add :menu_order, :integer
|
||||
add :post_type, :text
|
||||
add :post_mime_type, :text
|
||||
add :comment_count, :integer
|
||||
end
|
||||
|
||||
create table("wp_term_relationships", primary_key: false) do
|
||||
add :object_id, :serial, primary_key: true
|
||||
add :term_taxonomy_id, :integer, [:primary_key]
|
||||
add :term_order, :integer
|
||||
end
|
||||
|
||||
create table("wp_term_taxonomy", primary_key: false) do
|
||||
add :term_taxonomy_id, :serial, primary_key: true
|
||||
add :term_id, :integer
|
||||
add :taxonomy, :text
|
||||
add :description, :text
|
||||
add :parent, :integer
|
||||
add :count, :integer
|
||||
end
|
||||
|
||||
create table("wp_termmeta", primary_key: false) do
|
||||
add :meta_id, :serial, primary_key: true
|
||||
add :term_id, :integer
|
||||
add :meta_key, :text
|
||||
add :meta_value, :text
|
||||
end
|
||||
|
||||
create table("wp_terms", primary_key: false) do
|
||||
add :term_id, :serial, primary_key: true
|
||||
add :name, :text
|
||||
add :slug, :text
|
||||
add :term_group, :integer
|
||||
end
|
||||
|
||||
create table("wp_usermeta", primary_key: false) do
|
||||
add :umeta_id, :serial, primary_key: true
|
||||
add :user_id, :integer
|
||||
add :meta_key, :text
|
||||
add :meta_value, :text
|
||||
end
|
||||
|
||||
create table("wp_users", primary_key: false) do
|
||||
add :ID, :integer, [:primary_key]
|
||||
add :user_login, :text
|
||||
add :user_pass, :text
|
||||
add :user_nicename, :text
|
||||
add :user_email, :text
|
||||
add :user_url, :text
|
||||
add :user_registered, :naive_datetime
|
||||
add :user_activation_key, :text
|
||||
add :user_status, :integer
|
||||
add :display_name, :text
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Content.Repo.Migrations.ChangeCommentContentToText do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table("wp_comments") do
|
||||
modify :comment_content, :text
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Content.Repo.Migrations.AddDefaultToCommentCount do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table("wp_posts") do
|
||||
modify :comment_count, :integer, default: 0
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Content.Repo.Migrations.AddMetaValueToWpCommentmetas do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table("wp_commentmeta") do
|
||||
add :meta_value, :string
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Content.Repo.Migrations.AddLinkNotesToWpLinks do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table("wp_links") do
|
||||
add :link_notes, :string
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
defmodule Content.Repo.Migrations.AddUniqueIndexToPostName do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create unique_index(:wp_posts, ["post_name"])
|
||||
end
|
||||
end
|
11
apps/content/priv/repo/seeds.exs
Normal file
11
apps/content/priv/repo/seeds.exs
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Script for populating the database. You can run it as:
|
||||
#
|
||||
# mix run priv/repo/seeds.exs
|
||||
#
|
||||
# Inside the script, you can read and write to any of your
|
||||
# repositories directly:
|
||||
#
|
||||
# Content.Repo.insert!(%Content.SomeSchema{})
|
||||
#
|
||||
# We recommend using the bang functions (`insert!`, `update!`
|
||||
# and so on) as they will fail if something goes wrong.
|
69
apps/content/test/content/attachment_test.exs
Normal file
69
apps/content/test/content/attachment_test.exs
Normal file
|
@ -0,0 +1,69 @@
|
|||
defmodule Content.AttachmentTest do
|
||||
use Content.DataCase
|
||||
|
||||
alias Content.{Attachment, Postmeta, Posts, Repo}
|
||||
|
||||
@create_attrs %{
|
||||
ID: 123,
|
||||
post_name: "my-attachment",
|
||||
post_title: "My Attachment",
|
||||
post_content: "",
|
||||
post_status: "publish",
|
||||
post_type: "attachment",
|
||||
post_date: "2018-01-01T00:00:00Z"
|
||||
}
|
||||
|
||||
def fixture(:wide_attachment) do
|
||||
{:ok, attachment} = Posts.create_posts(@create_attrs)
|
||||
{:ok, _meta} =
|
||||
%Postmeta{
|
||||
post_id: attachment."ID",
|
||||
meta_key: "_wp_attachment_metadata",
|
||||
meta_value: "a:2:{s:5:\"width\";i:640;s:6:\"height\";i:480;}"
|
||||
} |> Repo.insert()
|
||||
|
||||
Content.Post
|
||||
|> preload([:metas])
|
||||
|> Repo.get!(attachment."ID")
|
||||
end
|
||||
|
||||
def fixture(:tall_attachment) do
|
||||
{:ok, attachment} = Posts.create_posts(@create_attrs)
|
||||
{:ok, _meta} =
|
||||
%Postmeta{
|
||||
post_id: attachment."ID",
|
||||
meta_key: "_wp_attachment_metadata",
|
||||
meta_value: "a:2:{s:5:\"width\";i:480;s:6:\"height\";i:640;}"
|
||||
} |> Repo.insert()
|
||||
Content.Post
|
||||
|> preload([:metas])
|
||||
|> Repo.get!(attachment."ID")
|
||||
end
|
||||
|
||||
def fixture(:unknown_dimensions) do
|
||||
{:ok, attachment} = Posts.create_posts(@create_attrs)
|
||||
Content.Post
|
||||
|> preload([:metas])
|
||||
|> Repo.get!(attachment."ID")
|
||||
end
|
||||
|
||||
describe "dimensions" do
|
||||
test "can get dimensions" do
|
||||
assert Attachment.dimensions(fixture(:wide_attachment)) == %{width: 640, height: 480}
|
||||
end
|
||||
|
||||
test "returns nil if dimensions are missing" do
|
||||
assert is_nil(Attachment.dimensions(fixture(:unknown_dimensions)))
|
||||
end
|
||||
end
|
||||
|
||||
describe "vertical?" do
|
||||
test "returns true if vertical image" do
|
||||
assert Attachment.vertical?(fixture(:tall_attachment))
|
||||
end
|
||||
|
||||
test "returns false if not vertical image" do
|
||||
refute Attachment.vertical?(fixture(:wide_attachment))
|
||||
end
|
||||
end
|
||||
end
|
15
apps/content/test/content/commentmeta_test.exs
Normal file
15
apps/content/test/content/commentmeta_test.exs
Normal file
|
@ -0,0 +1,15 @@
|
|||
defmodule Content.CommentmetaTest do
|
||||
use Content.DataCase
|
||||
|
||||
alias Content.{Commentmeta, Repo}
|
||||
|
||||
test "can save a new commentmeta" do
|
||||
%Commentmeta{}
|
||||
|> Commentmeta.changeset(%{
|
||||
comment_id: 123,
|
||||
meta_key: "testcommentmeta",
|
||||
meta_value: "some value",
|
||||
})
|
||||
|> Repo.insert!()
|
||||
end
|
||||
end
|
53
apps/content/test/content/comments_test.exs
Normal file
53
apps/content/test/content/comments_test.exs
Normal file
|
@ -0,0 +1,53 @@
|
|||
defmodule Content.CommentsTest do
|
||||
use Content.DataCase
|
||||
|
||||
alias Content.{Comment, Comments, Repo}
|
||||
alias Ecto.Changeset
|
||||
|
||||
def fixture(:parent_comment) do
|
||||
%Comment{
|
||||
comment_ID: 123,
|
||||
comment_content: "Hello world",
|
||||
comment_post_ID: 456,
|
||||
}
|
||||
|> Repo.insert!()
|
||||
end
|
||||
|
||||
def fixture(:child_comment) do
|
||||
%Comment{
|
||||
comment_ID: 456,
|
||||
comment_parent: 123,
|
||||
comment_content: "Hello back",
|
||||
comment_post_ID: 456,
|
||||
}
|
||||
|> Repo.insert!()
|
||||
end
|
||||
|
||||
describe "children" do
|
||||
test "can get children of a comment that has them" do
|
||||
parent = fixture(:parent_comment)
|
||||
kid = fixture(:child_comment)
|
||||
|
||||
kids = Comments.children(parent.comment_ID, Comments.list_comments)
|
||||
assert kids == [kid]
|
||||
end
|
||||
|
||||
test "returns an empty list if the comment has no children " do
|
||||
parent = fixture(:parent_comment)
|
||||
|
||||
kids = Comments.children(parent.comment_ID, Comments.list_comments)
|
||||
assert kids == []
|
||||
end
|
||||
end
|
||||
|
||||
describe "change comment" do
|
||||
test "gives a changeset" do
|
||||
changeset = fixture(:parent_comment) |> Comments.change_comment
|
||||
changed_value =
|
||||
changeset
|
||||
|> Changeset.put_change(:comment_content, "woops")
|
||||
|> Changeset.get_change(:comment_content)
|
||||
assert changed_value == "woops"
|
||||
end
|
||||
end
|
||||
end
|
13
apps/content/test/content/link_test.exs
Normal file
13
apps/content/test/content/link_test.exs
Normal file
|
@ -0,0 +1,13 @@
|
|||
defmodule Content.LinkTest do
|
||||
use Content.DataCase
|
||||
|
||||
alias Content.{Link, Repo}
|
||||
|
||||
test "can save a new link" do
|
||||
%Link{}
|
||||
|> Link.changeset(%{
|
||||
link_url: "https://example.com"
|
||||
})
|
||||
|> Repo.insert!()
|
||||
end
|
||||
end
|
141
apps/content/test/content/menu_test.exs
Normal file
141
apps/content/test/content/menu_test.exs
Normal file
|
@ -0,0 +1,141 @@
|
|||
defmodule Content.MenuTest do
|
||||
use Content.DataCase
|
||||
|
||||
alias Content.{Menu, Option, Post, Postmeta, Repo, Term, TermRelationship}
|
||||
|
||||
@theme_option %Option{
|
||||
option_name: "test_theme",
|
||||
option_value: "a:1:{s:18:\"nav_menu_locations\";a:1:{s:3:\"top\";i:13;}}"
|
||||
}
|
||||
|
||||
@term_relationship %TermRelationship{
|
||||
term_taxonomy_id: 13,
|
||||
object_id: 123,
|
||||
}
|
||||
|
||||
@top_nav_item %Post{
|
||||
ID: 123,
|
||||
post_name: "home",
|
||||
post_title: "Home",
|
||||
post_content: "",
|
||||
post_status: "publish",
|
||||
post_type: "nav_item",
|
||||
post_date: ~N"2018-01-01T00:00:00",
|
||||
comment_status: "open",
|
||||
}
|
||||
|
||||
@top_nav_metas [
|
||||
%{
|
||||
post_id: 123,
|
||||
meta_key: "_menu_item_object_id",
|
||||
meta_value: "456",
|
||||
},
|
||||
%{
|
||||
post_id: 123,
|
||||
meta_key: "_menu_item_object",
|
||||
meta_value: "post",
|
||||
},
|
||||
%{
|
||||
post_id: 123,
|
||||
meta_key: "_menu_item_menu_item_parent",
|
||||
meta_value: "0",
|
||||
},
|
||||
]
|
||||
|
||||
@category_nav_metas [
|
||||
%{
|
||||
post_id: 123,
|
||||
meta_key: "_menu_item_object_id",
|
||||
meta_value: "42",
|
||||
},
|
||||
%{
|
||||
post_id: 123,
|
||||
meta_key: "_menu_item_object",
|
||||
meta_value: "category",
|
||||
},
|
||||
%{
|
||||
post_id: 123,
|
||||
meta_key: "_menu_item_menu_item_parent",
|
||||
meta_value: "0",
|
||||
},
|
||||
]
|
||||
|
||||
@related_page %Post {
|
||||
ID: 456,
|
||||
post_title: "Test Nav Home",
|
||||
}
|
||||
|
||||
@related_category %Term{
|
||||
term_id: 42,
|
||||
name: "Test Category",
|
||||
slug: "test-category",
|
||||
}
|
||||
|
||||
def fixture(:option) do
|
||||
@theme_option |> Repo.insert()
|
||||
end
|
||||
|
||||
def fixture(:menu) do
|
||||
{:ok, option} = fixture(:option)
|
||||
{:ok, _term_relationship} = @term_relationship |> Repo.insert()
|
||||
{:ok, _nav_item} = @top_nav_item |> Repo.insert()
|
||||
{3, nil} = Repo.insert_all(Postmeta, @top_nav_metas)
|
||||
{:ok, _post} = @related_page |> Repo.insert()
|
||||
option
|
||||
end
|
||||
|
||||
def fixture(:category_menu) do
|
||||
{:ok, option} = fixture(:option)
|
||||
{:ok, _term_relationship} = @term_relationship |> Repo.insert()
|
||||
{:ok, _nav_item} = @top_nav_item |> Repo.insert()
|
||||
{3, nil} = Repo.insert_all(Postmeta, @category_nav_metas)
|
||||
{:ok, _category} = @related_category |> Repo.insert()
|
||||
option
|
||||
end
|
||||
|
||||
describe "get_menu_from_option_and_location" do
|
||||
test "returns an empty if the menu is not present" do
|
||||
fixture(:option)
|
||||
assert Menu.get_menu_from_option_and_location("test_theme", "top") == []
|
||||
end
|
||||
|
||||
test "returns items if the menu is present" do
|
||||
fixture(:menu)
|
||||
menu = Menu.get_menu_from_option_and_location("test_theme", "top")
|
||||
refute menu == []
|
||||
assert (menu |> Enum.at(0)) == %{
|
||||
children: [],
|
||||
parent_id: "0",
|
||||
post_id: 123,
|
||||
related_item: %{resource: "posts", slug: nil, title: "Test Nav Home"},
|
||||
target_id: "456",
|
||||
type: "post",
|
||||
url: nil,
|
||||
}
|
||||
end
|
||||
|
||||
test "returns items if the menu has a category" do
|
||||
fixture(:category_menu)
|
||||
menu = Menu.get_menu_from_option_and_location("test_theme", "top")
|
||||
refute menu == []
|
||||
assert (menu |> Enum.at(0)) == %{
|
||||
children: [],
|
||||
parent_id: "0",
|
||||
post_id: 123,
|
||||
related_item: %{resource: "category", slug: "test-category", title: "Test Category"},
|
||||
target_id: "42",
|
||||
type: "category",
|
||||
url: nil,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "put_menu_option" do
|
||||
test "it can change the active menu in a position" do
|
||||
fixture(:menu)
|
||||
|
||||
{:ok, _option} = Menu.put_menu_option("test_theme", "top", 7) |> Repo.update()
|
||||
assert Menu.get_menu_from_option_and_location("test_theme", "top") == []
|
||||
end
|
||||
end
|
||||
end
|
14
apps/content/test/content/option_test.exs
Normal file
14
apps/content/test/content/option_test.exs
Normal file
|
@ -0,0 +1,14 @@
|
|||
defmodule Content.OptionTest do
|
||||
use Content.DataCase
|
||||
|
||||
alias Content.{Option, Repo}
|
||||
|
||||
test "can save a new link" do
|
||||
%Option{}
|
||||
|> Option.changeset(%{
|
||||
option_name: "test_up",
|
||||
option_value: "1",
|
||||
})
|
||||
|> Repo.insert!()
|
||||
end
|
||||
end
|
26
apps/content/test/content/options_test.exs
Normal file
26
apps/content/test/content/options_test.exs
Normal file
|
@ -0,0 +1,26 @@
|
|||
defmodule Content.OptionsTest do
|
||||
use Content.DataCase
|
||||
|
||||
alias Content.{Option, Options, Repo}
|
||||
|
||||
def fixture(:option) do
|
||||
%Option{}
|
||||
|> Option.changeset(%{
|
||||
option_name: "test_up",
|
||||
option_value: "1",
|
||||
})
|
||||
|> Repo.insert!()
|
||||
end
|
||||
|
||||
test "can get an option by name" do
|
||||
fixture(:option)
|
||||
|
||||
assert Options.get_value("test_up") == "1"
|
||||
end
|
||||
|
||||
test "can get an option by name as an int" do
|
||||
fixture(:option)
|
||||
|
||||
assert Options.get_value_as_int("test_up") == {1, ""}
|
||||
end
|
||||
end
|
15
apps/content/test/content/postmeta_test.exs
Normal file
15
apps/content/test/content/postmeta_test.exs
Normal file
|
@ -0,0 +1,15 @@
|
|||
defmodule Content.PostmetaTest do
|
||||
use Content.DataCase
|
||||
|
||||
alias Content.{Postmeta, Repo}
|
||||
|
||||
test "can save a new postmeta" do
|
||||
%Postmeta{}
|
||||
|> Postmeta.changeset(%{
|
||||
post_id: 123,
|
||||
meta_key: "testpostmeta",
|
||||
meta_value: "some value",
|
||||
})
|
||||
|> Repo.insert!()
|
||||
end
|
||||
end
|
71
apps/content/test/content/shortcode_test.exs
Normal file
71
apps/content/test/content/shortcode_test.exs
Normal file
|
@ -0,0 +1,71 @@
|
|||
defmodule Content.ShortcodesTest do
|
||||
use ExUnit.Case
|
||||
|
||||
import Content.Shortcodes
|
||||
|
||||
describe "shortcodes" do
|
||||
test "no shortcodes, no problem" do
|
||||
assert expand_shortcodes("test") == "test"
|
||||
end
|
||||
|
||||
test "double bracket escapes work" do
|
||||
assert expand_shortcodes("[[test]]") == "[test]"
|
||||
end
|
||||
|
||||
test "escapes enclosing shortcodes" do
|
||||
assert expand_shortcodes("[[test] abc [/test]]") == "[test] abc [/test]"
|
||||
end
|
||||
|
||||
test "escapes shortcodes with args" do
|
||||
assert expand_shortcodes("[[test with-args][/test]]") == "[test with-args][/test]"
|
||||
end
|
||||
|
||||
test "expands shortcodes" do
|
||||
assert expand_shortcodes("[test]") == "TSET"
|
||||
end
|
||||
|
||||
test "expands shortcodes in the middle" do
|
||||
assert expand_shortcodes("this is a [test] of the shortcode system") == "this is a TSET of the shortcode system"
|
||||
end
|
||||
|
||||
test "expands shortcodes at the end" do
|
||||
assert expand_shortcodes("this is a [test]") == "this is a TSET"
|
||||
end
|
||||
|
||||
test "expands shortcodes at the beginning" do
|
||||
assert expand_shortcodes("[test] it up") == "TSET it up"
|
||||
end
|
||||
|
||||
test "handles shortcodes with args" do
|
||||
assert expand_shortcodes("[test with-args]") == "TSET"
|
||||
end
|
||||
|
||||
test "handles enclosing shortcodes" do
|
||||
assert expand_shortcodes("[test]Content[/test]") == "TNETNOC"
|
||||
end
|
||||
|
||||
test "handles enclosing shortcodes with args" do
|
||||
assert expand_shortcodes("[test with-args]Content[/test]") == "TNETNOC"
|
||||
end
|
||||
|
||||
test "handles enclosing shortcodes with no content" do
|
||||
assert expand_shortcodes("[test with-args][/test]") == ""
|
||||
end
|
||||
|
||||
test "handles strings with carriage returns" do
|
||||
assert expand_shortcodes(" | \r\n ") == " | \n "
|
||||
end
|
||||
|
||||
test "handles strings with high unicode characters" do
|
||||
assert expand_shortcodes("—") == "—"
|
||||
end
|
||||
|
||||
test "handles shortcodes within tags" do
|
||||
assert expand_shortcodes("<p>[test]<em>chacha</em></p>") == "<p>TSET<em>chacha</em></p>"
|
||||
end
|
||||
|
||||
test "handles mangled shortcodes gracefully" do
|
||||
assert expand_shortcodes("[[unclosed shortcode") == "[[unclosed shortcode"
|
||||
end
|
||||
end
|
||||
end
|
91
apps/content/test/content/slugs_test.exs
Normal file
91
apps/content/test/content/slugs_test.exs
Normal file
|
@ -0,0 +1,91 @@
|
|||
defmodule Content.SlugsTest do
|
||||
use Content.DataCase
|
||||
|
||||
alias Content.{Post, Posts, Repo, Slugs}
|
||||
alias Ecto.Changeset
|
||||
|
||||
@create_attrs %{
|
||||
ID: 123,
|
||||
post_name: "my-post",
|
||||
post_title: "My Post",
|
||||
post_content: "",
|
||||
post_status: "publish",
|
||||
post_type: "post",
|
||||
post_date: "2018-01-01T00:00:00Z"
|
||||
}
|
||||
|
||||
@dupe_title_attrs %{
|
||||
ID: 456,
|
||||
post_title: "My Post",
|
||||
post_content: "",
|
||||
post_status: "publish",
|
||||
post_type: "post",
|
||||
post_date: "2018-01-01T00:00:00Z"
|
||||
}
|
||||
|
||||
describe "ensure_post_has_slug" do
|
||||
test "doesn't overwrite a set slug" do
|
||||
new_post =
|
||||
%Post{
|
||||
post_name: "a-set-slug"
|
||||
}
|
||||
|> Post.changeset()
|
||||
|> Slugs.ensure_post_has_slug()
|
||||
|> Changeset.apply_changes()
|
||||
|
||||
assert new_post.post_name == "a-set-slug"
|
||||
end
|
||||
|
||||
test "works even if the title is nil" do
|
||||
new_post =
|
||||
%Post{}
|
||||
|> Changeset.change(%{})
|
||||
|> Slugs.ensure_post_has_slug()
|
||||
|> Changeset.apply_changes()
|
||||
|
||||
assert new_post.post_name |> String.length() > 0
|
||||
end
|
||||
|
||||
test "sets a slug if the title is there" do
|
||||
new_post =
|
||||
%Post{
|
||||
post_title: "My NEW Post"
|
||||
}
|
||||
|> Changeset.change(%{})
|
||||
|> Slugs.ensure_post_has_slug()
|
||||
|> Changeset.apply_changes()
|
||||
|
||||
assert new_post.post_name == "my-new-post"
|
||||
end
|
||||
|
||||
test "ensures uniqueness of the slug" do
|
||||
{:ok, og_post} = Posts.create_posts(@create_attrs)
|
||||
assert Post |> Repo.aggregate(:count, :ID) == 1
|
||||
|
||||
new_post =
|
||||
%Post{
|
||||
post_title: "MY POST"
|
||||
}
|
||||
|> Changeset.change(%{})
|
||||
|> Slugs.ensure_post_has_slug()
|
||||
|> Changeset.apply_changes()
|
||||
|
||||
assert new_post.post_name != og_post.post_name
|
||||
assert new_post.post_name == "my-post-1"
|
||||
end
|
||||
|
||||
test "ensures uniqueness of the slug on update" do
|
||||
{:ok, og_post} = Posts.create_posts(@create_attrs)
|
||||
assert Post |> Repo.aggregate(:count, :ID) == 1
|
||||
|
||||
new_post =
|
||||
%Post{}
|
||||
|> Changeset.change(@dupe_title_attrs)
|
||||
|> Slugs.ensure_post_has_slug()
|
||||
|> Changeset.apply_changes()
|
||||
|
||||
assert new_post.post_name != og_post.post_name
|
||||
assert new_post.post_name == "my-post-1"
|
||||
end
|
||||
end
|
||||
end
|
14
apps/content/test/content/term_relationship_test.exs
Normal file
14
apps/content/test/content/term_relationship_test.exs
Normal file
|
@ -0,0 +1,14 @@
|
|||
defmodule Content.TermRelationshipTest do
|
||||
use Content.DataCase
|
||||
|
||||
alias Content.{Repo, TermRelationship}
|
||||
|
||||
test "can save a new term relationship" do
|
||||
%TermRelationship{}
|
||||
|> TermRelationship.changeset(%{
|
||||
object_id: 123,
|
||||
term_taxonomy_id: 456,
|
||||
})
|
||||
|> Repo.insert!()
|
||||
end
|
||||
end
|
13
apps/content/test/content/term_taxonomy_test.exs
Normal file
13
apps/content/test/content/term_taxonomy_test.exs
Normal file
|
@ -0,0 +1,13 @@
|
|||
defmodule Content.TermTaxonomyTest do
|
||||
use Content.DataCase
|
||||
|
||||
alias Content.{Repo, TermTaxonomy}
|
||||
|
||||
test "can save a new term taxonomy" do
|
||||
%TermTaxonomy{}
|
||||
|> TermTaxonomy.changeset(%{
|
||||
taxonomy: "post_tag",
|
||||
})
|
||||
|> Repo.insert!()
|
||||
end
|
||||
end
|
14
apps/content/test/content/term_test.exs
Normal file
14
apps/content/test/content/term_test.exs
Normal file
|
@ -0,0 +1,14 @@
|
|||
defmodule Content.TermTest do
|
||||
use Content.DataCase
|
||||
|
||||
alias Content.{Repo, Term}
|
||||
|
||||
test "can save a new term" do
|
||||
%Term{}
|
||||
|> Term.changeset(%{
|
||||
slug: "testterm",
|
||||
name: "Test Term",
|
||||
})
|
||||
|> Repo.insert!()
|
||||
end
|
||||
end
|
15
apps/content/test/content/termmeta_test.exs
Normal file
15
apps/content/test/content/termmeta_test.exs
Normal file
|
@ -0,0 +1,15 @@
|
|||
defmodule Content.TermmetaTest do
|
||||
use Content.DataCase
|
||||
|
||||
alias Content.{Repo, Termmeta}
|
||||
|
||||
test "can save a new termmeta" do
|
||||
%Termmeta{}
|
||||
|> Termmeta.changeset(%{
|
||||
term_id: 123,
|
||||
meta_key: "testtermmeta",
|
||||
meta_value: "some value",
|
||||
})
|
||||
|> Repo.insert!()
|
||||
end
|
||||
end
|
56
apps/content/test/support/data_case.ex
Normal file
56
apps/content/test/support/data_case.ex
Normal file
|
@ -0,0 +1,56 @@
|
|||
defmodule Content.DataCase do
|
||||
@moduledoc """
|
||||
This module defines the setup for tests requiring
|
||||
access to the application's data layer.
|
||||
|
||||
You may define functions here to be used as helpers in
|
||||
your tests.
|
||||
|
||||
Finally, if the test case interacts with the database,
|
||||
it cannot be async. For this reason, every test runs
|
||||
inside a transaction which is reset at the beginning
|
||||
of the test unless the test case is marked as async.
|
||||
"""
|
||||
|
||||
use ExUnit.CaseTemplate
|
||||
|
||||
alias Ecto.Adapters.SQL.Sandbox
|
||||
alias Ecto.Changeset
|
||||
|
||||
using do
|
||||
quote do
|
||||
alias Content.Repo
|
||||
|
||||
import Ecto
|
||||
|
||||
import Ecto.Query
|
||||
import Content.DataCase
|
||||
end
|
||||
end
|
||||
|
||||
setup tags do
|
||||
:ok = Sandbox.checkout(Content.Repo)
|
||||
|
||||
unless tags[:async] do
|
||||
Sandbox.mode(Content.Repo, {:shared, self()})
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
A helper that transform changeset errors to a map of messages.
|
||||
|
||||
assert {:error, changeset} = Accounts.create_user(%{password: "short"})
|
||||
assert "password is too short" in errors_on(changeset).password
|
||||
assert %{password: ["password is too short"]} = errors_on(changeset)
|
||||
|
||||
"""
|
||||
def errors_on(changeset) do
|
||||
Changeset.traverse_errors(changeset, fn {message, opts} ->
|
||||
Enum.reduce(opts, message, fn {key, value}, acc ->
|
||||
String.replace(acc, "%{#{key}}", to_string(value))
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
3
apps/content/test/test_helper.exs
Normal file
3
apps/content/test/test_helper.exs
Normal file
|
@ -0,0 +1,3 @@
|
|||
ExUnit.start()
|
||||
|
||||
Ecto.Adapters.SQL.Sandbox.mode(Content.Repo, :manual)
|
|
@ -16,6 +16,7 @@ defmodule ContentWeb.Router do
|
|||
scope "/", ContentWeb do
|
||||
pipe_through :browser
|
||||
|
||||
get "/posts/:id", PostsController, :show
|
||||
get "/:id", PageController, :show
|
||||
end
|
||||
end
|
||||
|
|
26
mix.lock
26
mix.lock
|
@ -1,47 +1,73 @@
|
|||
%{
|
||||
"bamboo": {:hex, :bamboo, "1.5.0", "1926107d58adba6620450f254dfe8a3686637a291851fba125686fa8574842af", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d5f3d04d154e80176fd685e2531e73870d8700679f14d25a567e448abce6298d"},
|
||||
"bcrypt_elixir": {:hex, :bcrypt_elixir, "1.1.1", "6b5560e47a02196ce5f0ab3f1d8265db79a23868c137e973b27afef928ed8006", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "10f658be786bd2daaadcd45cc5b598da01d5bbc313da4d0e3efb2d6a511d896d"},
|
||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
|
||||
"certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"},
|
||||
"cldr_utils": {:hex, :cldr_utils, "2.9.1", "be714403abe1a7abed5ee4f7dd3823a9067f96ab4b0613a454177b51ca204236", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "6cba0a485f57feb773291ca1816469ddd887e22d73d9b12a1b207d82a67a4e71"},
|
||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
||||
"comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm", "d8700a0ca4dbb616c22c9b3f6dd539d88deaafec3efe66869d6370c9a559b3e9"},
|
||||
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
|
||||
"cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},
|
||||
"cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"},
|
||||
"credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"},
|
||||
"crontab": {:hex, :crontab, "1.1.10", "dc9bb1f4299138d47bce38341f5dcbee0aa6c205e864fba7bc847f3b5cb48241", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "1347d889d1a0eda997990876b4894359e34bfbbd688acbb0ba28a2795ca40685"},
|
||||
"db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
|
||||
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
|
||||
"earmark": {:hex, :earmark, "1.4.10", "bddce5e8ea37712a5bfb01541be8ba57d3b171d3fa4f80a0be9bcf1db417bcaf", [:mix], [{:earmark_parser, ">= 1.4.10", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "12dbfa80810478e521d3ffb941ad9fbfcbbd7debe94e1341b4c4a1b2411c1c27"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
|
||||
"ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.4.4", "d28bac2d420f708993baed522054870086fd45016a9d09bb2cd521b9c48d32ea", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb49af715dd72f213b66adfd0f668a43c17ed510b5d9ac7528569b23af57fe8"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
|
||||
"ex_cldr": {:hex, :ex_cldr, "2.13.0", "742f14a4afcfea61a190d603d8e555d2c91d71e4e8fc2520d5dc35616969e225", [:mix], [{:cldr_utils, "~> 2.3", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "5e4cf3e945ee60156a3342e2a762f69036ffbe1f80520cc88592d68f12c5db55"},
|
||||
"ex_prompt": {:hex, :ex_prompt, "0.1.5", "b136642d0962f8ea37b3c9fa185ad1f42c71c3b9c6c3950f0358d7f3d2db2970", [:mix], [], "hexpm", "ad19a404708c9c7b05d36090b2d074ceafbed248a8de1a22d45a05ebe6994b83"},
|
||||
"excoveralls": {:hex, :excoveralls, "0.13.0", "4e1b7cc4e0351d8d16e9be21b0345a7e165798ee5319c7800b9138ce17e0b38e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "fe2a56c8909564e2e6764765878d7d5e141f2af3bc8ff3b018a68ee2a218fced"},
|
||||
"file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"},
|
||||
"floki": {:hex, :floki, "0.25.0", "b1c9ddf5f32a3a90b43b76f3386ca054325dc2478af020e87b5111c19f2284ac", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "631f4e627c46d5ecd347df5a2accdaf0621c77c3693c5b75a8ad58e84c61f242"},
|
||||
"gen_stage": {:hex, :gen_stage, "1.0.0", "51c8ae56ff54f9a2a604ca583798c210ad245f415115453b773b621c49776df5", [:mix], [], "hexpm", "1d9fc978db5305ac54e6f5fec7adf80cd893b1000cf78271564c516aa2af7706"},
|
||||
"gen_state_machine": {:hex, :gen_state_machine, "2.1.0", "a38b0e53fad812d29ec149f0d354da5d1bc0d7222c3711f3a0bd5aa608b42992", [:mix], [], "hexpm", "ae367038808db25cee2f2c4b8d0531522ea587c4995eb6f96ee73410a60fa06b"},
|
||||
"gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"},
|
||||
"hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [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]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"},
|
||||
"html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
|
||||
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.0.1", "2572e7122c78ab7e57b613e7c7f5e42bf9b3c25e430e32f23f1413d86db8a0af", [:mix], [{:mochiweb, "~> 2.12.2", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "c334e2835e094fb9c04658bd4cfc7533fa51a8f56f11343c57ab9cb2a01d8613"},
|
||||
"idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"},
|
||||
"jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"},
|
||||
"libring": {:hex, :libring, "1.5.0", "44313eb6862f5c9168594a061e9d5f556a9819da7c6444706a9e2da533396d70", [:mix], [], "hexpm", "04e843d4fdcff49a62d8e03778d17c6cb2a03fe2d14020d3825a1761b55bd6cc"},
|
||||
"linguist": {:hex, :linguist, "0.3.0", "2984dfce6720d1212ddd7bba82496f92627a39aecd4d32c7016ec00393e1f925", [:mix], [{:ex_cldr, "~> 2.0", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.0", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "1923876545db22b63334c9d203ef56397a2946daa018117767b068f856be41e4"},
|
||||
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
|
||||
"mochiweb": {:hex, :mochiweb, "2.12.2", "80804ad342afa3d7f3524040d4eed66ce74b17a555de454ac85b07c479928e46", [:make, :rebar], [], "hexpm", "d3e681d4054b74a96cf2efcd09e94157ab83a5f55ddc4ce69f90b8144673bd7a"},
|
||||
"mock": {:hex, :mock, "0.3.5", "feb81f52b8dcf0a0d65001d2fec459f6b6a8c22562d94a965862f6cc066b5431", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "6fae404799408300f863550392635d8f7e3da6b71abdd5c393faf41b131c8728"},
|
||||
"neotomex": {:hex, :neotomex, "0.1.7", "64f76513653aa87ea7abdde0fd600e56955d838020a13d88f2bf334c88ac3e7a", [:mix], [], "hexpm", "4b87b8f614d1cd89dc8ba80ba0e559bedb3ebf6f6d74cd774fcfdd215e861445"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
|
||||
"phoenix": {:hex, :phoenix, "1.5.3", "bfe0404e48ea03dfe17f141eff34e1e058a23f15f109885bbdcf62be303b49ff", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8e16febeb9640d8b33895a691a56481464b82836d338bb3a23125cd7b6157c25"},
|
||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [: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", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"},
|
||||
"phoenix_html_sanitizer": {:hex, :phoenix_html_sanitizer, "1.0.2", "e2c8cfbc83660e362753de127cc957bec3442a8aecdf271fb65a684a906fccf5", [:mix], [{:html_sanitize_ex, "~> 1.0.0", [hex: :html_sanitize_ex, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "47aebb08fa954b7ad95f295fb701df9800ee3a489212119c9c6074a65e1e5a10"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.2.6", "1b4e1b7d797386b7f9d70d2af931dc9843a5f2f2423609d22cef1eec4e4dba7d", [:mix], [{:phoenix_html, "~> 2.14.1 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.13.1", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.4.0 or ~> 0.5.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "b20dcad98c4ca63d38a7f5e7a40936e1e8e9da983d3d722b88ae33afb866c9ca"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.4", "940c0344b1d66a2e46eef02af3a70e0c5bb45a4db0bf47917add271b76cd3914", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "38f9308357dea4cc77f247e216da99fcb0224e05ada1469167520bed4cb8cccd"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.13.3", "2186c55cc7c54ca45b97c6f28cfd267d1c61b5f205f3c83533704cd991bdfdec", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.4.17 or ~> 1.5.2", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "c6309a7da2e779cb9cdf2fb603d75f38f49ef324bedc7a81825998bd1744ff8a"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
||||
"php_serializer": {:hex, :php_serializer, "0.9.2", "59c5fd6bd3096671fd89358fb8229341ac7423b50ad8d45a15213b02ea2edab2", [:mix], [], "hexpm", "34eb835a460944f7fc216773b363c02e7dcf8ac0390c9e9ccdbd92b31a7ca59a"},
|
||||
"plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
|
||||
"postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"},
|
||||
"pow": {:hex, :pow, "1.0.20", "b99993811af5233681bfc521e81ca706d25a56f2be54bad6424db327ce840ab9", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.3.0 and < 1.6.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", "4b6bd271399ccb353abbdbdc316199fe7fd7ae36bbf47059d53e366831c34fc8"},
|
||||
"quantum": {:hex, :quantum, "2.4.0", "f2ad4b20988f848455d35ed0e884ba0c7629a27ee86cbec6a6e0fc214b6e69cf", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12 or ~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: true]}], "hexpm", "a125a9e65a5af740a1198f3b05c1a736fce3942f5e0dc2901e0f9be5745bea99"},
|
||||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
|
||||
"sitemap": {:hex, :sitemap, "1.1.0", "23a019cccef7c17090d0b493354ee47a94549db64fd1cf39bda7eb41c567729c", [:mix], [{:xml_builder, ">= 0.0.0", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm", "d21f2c3ac65567fbdbe231f9faaf802a48405aa487d24052964d3d818a3d8c22"},
|
||||
"slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm", "20d0ded0e712605d1eae6c5b4889581c3460d92623a930ddda91e0e609b5afba"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||
"swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm", "94884f84783fc1ba027aba8fe8a7dae4aad78c98e9f9c76667ec3471585c08c6"},
|
||||
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.5.0", "1b796e74add83abf844e808564275dfb342bcc930b04c7577ab780e262b0d998", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31225e6ce7a37a421a0a96ec55244386aec1c190b22578bd245188a4a33298fd"},
|
||||
"telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"},
|
||||
"timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"},
|
||||
"tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"},
|
||||
"xml_builder": {:hex, :xml_builder, "2.1.2", "90cb9ad382958934c78c6ddfbe6d385a8ce147d84b61cbfa83ec93a169d0feab", [:mix], [], "hexpm", "b89046041da2fbc1d51d31493ba31b9d5fc6223c93384bf513a1a9e1df9ec081"},
|
||||
"yamerl": {:hex, :yamerl, "0.8.0", "8214cfe16bbabe5d1d6c14a14aea11c784b9a21903dd6a7c74f8ce180adae5c7", [:rebar3], [], "hexpm", "010634477bf9c208a0767dcca89116c2442cf0b5e87f9c870f85cd1c3e0c2aab"},
|
||||
"yaml_elixir": {:hex, :yaml_elixir, "2.4.0", "2f444abc3c994c902851fde56b6a9cb82895c291c05a0490a289035c2e62ae71", [:mix], [{:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "4e25a6d5c873e393689c6f1062c5ec90f6cd1be2527b073178ae37eae4c78bee"},
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue