defmodule FrenzyWeb.PipelineController do use FrenzyWeb, :controller alias Frenzy.{Repo, Pipeline} alias FrenzyWeb.Endpoint import Ecto.Query plug :user_owns_pipeline defp user_owns_pipeline(%Plug.Conn{path_params: %{"id" => pipeline_id}} = conn, _opts) do user = conn.assigns[:user] pipeline = Repo.get(Pipeline, pipeline_id) if pipeline.user_id == user.id do conn |> assign(:pipeline, pipeline) else conn |> put_flash(:error, "You do not have permission to access that resource.") |> redirect(to: Routes.group_path(Endpoint, :index)) |> halt() end end defp user_owns_pipeline(conn, _opts), do: conn def index(conn, _params) do user = conn.assigns[:user] pipelines = Repo.all(from p in Pipeline, where: p.user_id == ^user.id, preload: [:feeds]) render(conn, "index.html", %{pipelines: pipelines}) end def new(conn, _params) do changeset = Pipeline.changeset(%Pipeline{}, %{ name: "", stages: "[\n]" }) render(conn, "new.html", %{changeset: changeset}) end def create(conn, %{"pipeline" => %{"name" => name, "stages" => stages_json}}) do user = conn.assigns[:user] with {:ok, stages} <- Jason.decode(stages_json), {:ok, stages} <- validate_pipeline(stages) do changeset = Pipeline.changeset(%Pipeline{}, %{user_id: user.id, name: name, stages: stages}) {:ok, _pipeline} = Repo.insert(changeset) conn |> put_flash(:info, "Pipeline created") |> redirect(to: Routes.pipeline_path(Endpoint, :index)) else {:error, reason} -> error_changeset = Pipeline.changeset(%Pipeline{}, %{name: name, stages: stages_json}) conn |> put_flash(:error, "Unable to create pipeline: #{reason}") |> render("new.html", %{changeset: error_changeset}) end end def show(conn, _params) do pipeline = conn.assigns[:pipeline] render(conn, "show.html", %{pipeline: pipeline}) end def delete(conn, _params) do conn.assigns[:pipeline] |> Repo.delete() redirect(conn, to: Routes.pipeline_path(Endpoint, :index)) end def edit(conn, _params) do pipeline = conn.assigns[:pipeline] {:ok, stages_json} = Jason.encode(pipeline.stages, pretty: true) render(conn, "edit.html", %{ pipeline: pipeline, name: pipeline.name, stages_json: stages_json }) end def update(conn, %{"pipeline" => %{"name" => name, "stages" => stages_json}}) do pipeline = conn.assigns[:pipeline] with {:ok, stages} <- Jason.decode(stages_json), {:ok, stages} <- validate_pipeline(stages) do changeset = Pipeline.changeset(pipeline, %{name: name, stages: stages}) {:ok, _pipeline} = Repo.update(changeset) conn |> put_flash(:info, "Pipeline edited") |> redirect(to: Routes.pipeline_path(Endpoint, :show, pipeline.id)) else {:error, reason} -> conn |> put_flash(:error, "Unable to edit pipeline: #{reason}") |> render("edit.html", %{ pipeline: pipeline, name: name, stages_json: stages_json }) end end defp validate_pipeline(stages) do stages |> Enum.with_index() |> Enum.reduce_while({:ok, []}, fn {stage, index}, {:ok, new_stages} -> case validate_stage(stage) do {:ok, stage} -> {:cont, {:ok, new_stages ++ [stage]}} {:error, reason} -> {:halt, {:error, "invalid stage at index #{index}: #{reason}"}} end end) end defp validate_stage(%{"module_name" => module_name, "options" => options}) do with true <- module_exists(module_name), {:ok, options} <- apply(String.to_existing_atom("Elixir." <> module_name), :validate_opts, [ options ]) do {:ok, %{"module_name" => module_name, "options" => options}} else false -> {:error, "module #{module_name} does not exist"} {:error, reason} -> {:error, "invalid options for #{module_name}: #{reason}"} end end defp validate_stage(_), do: {:error, "pipeline stage must be a map with module_name and options"} defp module_exists(module_name) do try do String.to_existing_atom("Elixir." <> module_name) true rescue ArgumentError -> false end end end