3.4 KiB · text History 9c0fc16
defmodule LambentExWeb.ServicesChannel do
  @moduledoc """
  Mirrors `LambentExWeb.ServicesLive.Index`: the configured mqtt / http /
  cronos services and the delete buttons.

  Cronos entries can be created here, the same way the web form creates them.
  Mqtt and http cannot yet.

  Pushes
    * `"services"` — `%{kind: "http" | "mqtt" | "cronos", services: ...}`
  """

  use Phoenix.Channel
  require Logger

  alias LambentEx.Schema.Cronos
  alias LambentExWeb.Channels.Payload

  @pubsub LambentEx.PubSub

  @impl true
  def join("services", _params, socket) do
    Phoenix.PubSub.subscribe(@pubsub, "svc_pub")
    Phoenix.PubSub.subscribe(@pubsub, "svc_idx")
    send(self(), :snapshot)
    {:ok, socket}
  end

  @impl true
  def handle_info(:snapshot, socket) do
    for {kind, svcs} <- [
          {"http", LambentEx.Meta.get_http()},
          {"mqtt", LambentEx.Meta.get_mqtt()},
          {"cronos", LambentEx.Meta.get_cronos()}
        ] do
      push(socket, "services", %{kind: kind, services: services(svcs)})
    end

    {:noreply, socket}
  end

  @impl true
  def handle_info({kind, svcs}, socket) when kind in [:http, :mqtt, :cronos] do
    push(socket, "services", %{kind: to_string(kind), services: services(svcs)})
    {:noreply, socket}
  end

  # stored as {name, entry} pairs, which would land on the wire as bare arrays
  defp services(list) when is_list(list) do
    Enum.map(list, fn
      {name, entry} -> %{name: to_string(name), entry: Payload.wire(entry)}
      other -> %{name: nil, entry: Payload.wire(other)}
    end)
  end

  defp services(other), do: Payload.wire(other)

  @impl true
  def handle_info({:mqtt_pub, svc}, socket) do
    push(socket, "service", %{kind: "mqtt", service: Payload.wire(svc)})
    {:noreply, socket}
  end

  @impl true
  def handle_info(_msg, socket), do: {:noreply, socket}

  # %{"cronos" => %{"name" =>, "time" => "22:00", "action" =>, "arg" =>, "targets" => []}}
  @impl true
  def handle_in("create_cronos", %{"cronos" => params}, socket) do
    case %Cronos{} |> Cronos.change(params) |> Cronos.save() do
      {:ok, entry} ->
        {:reply, {:ok, %{name: entry.name}}, socket}

      {:error, changeset} ->
        {:reply, {:error, %{errors: errors(changeset)}}, socket}
    end
  end

  # what a client needs to offer the same choices the web form does
  @impl true
  def handle_in("cronos_options", _params, socket) do
    push(socket, "cronos_options", %{
      actions: options(Cronos.actions()),
      args:
        Cronos.actions()
        |> Enum.map(fn {_label, action} ->
          {to_string(action), options(Cronos.arg_options(action))}
        end)
        |> Map.new()
    })

    {:reply, :ok, socket}
  end

  @impl true
  def handle_in("delete", %{"kind" => kind, "id" => id}, socket) do
    case kind do
      "http" -> LambentEx.Meta.del_http(id)
      "mqtt" -> LambentEx.MQTTSupervisor.remove(id)
      "cronos" -> LambentEx.Meta.del_cronos(id)
      other -> Logger.info("Need handler for this service kind: #{other}")
    end

    {:reply, :ok, socket}
  end

  defp options(pairs) do
    Enum.map(pairs, fn {label, value} -> %{label: to_string(label), value: to_string(value)} end)
  end

  defp errors(changeset) do
    Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
      Regex.replace(~r"%{(\w+)}", msg, fn _whole, key ->
        opts |> Keyword.get(String.to_existing_atom(key), "") |> to_string()
      end)
    end)
  end
end