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
 | 
					  scope "/", ContentWeb do
 | 
				
			||||||
    pipe_through :browser
 | 
					    pipe_through :browser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get "/posts/:id", PostsController, :show
 | 
				
			||||||
    get "/:id", PageController, :show
 | 
					    get "/:id", PageController, :show
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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": {: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"},
 | 
					  "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_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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
 | 
				
			||||||
  "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
 | 
					  "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
 | 
				
			||||||
  "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
 | 
					  "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"},
 | 
					  "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": {: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_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": {: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_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_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_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"},
 | 
					  "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": {: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_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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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": {: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_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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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"},
 | 
					  "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