Ошибки данных

[info] Sent 500 in 971ms
[error] #PID<0.530.0> running ImconWeb.Endpoint (connection #PID<0.529.0>, stream id 1) terminated
Server: caix.ru:4001 (http)
Request: GET /api/channels
** (exit) an exception was raised:
    ** (KeyError) key :__struct__ not found in: %{id: "1"}
        (ecto) lib/ecto.ex:502: Ecto.assoc/2
        (imcon) lib/imcon_web/services/channel_user_service.ex:24: ImconWeb.ChannelUserService.joined_channels_status/1
        (imcon) lib/imcon_web/controllers/api/channel_controller.ex:15: ImconWeb.ChannelController.index/2
        (imcon) lib/imcon_web/controllers/api/channel_controller.ex:1: ImconWeb.ChannelController.action/2
        (imcon) lib/imcon_web/controllers/api/channel_controller.ex:1: ImconWeb.ChannelController.phoenix_controller_pipeline/2
        (imcon) lib/imcon_web/endpoint.ex:1: ImconWeb.Endpoint.instrument/4
        (phoenix) lib/phoenix/router.ex:275: Phoenix.Router.__call__/1
        (imcon) lib/imcon_web/endpoint.ex:1: ImconWeb.Endpoint.plug_builder_call/2
        (imcon) lib/plug/debugger.ex:122: ImconWeb.Endpoint."call (overridable 3)"/2
        (imcon) lib/imcon_web/endpoint.ex:1: ImconWeb.Endpoint.call/2
        (phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:34: Phoenix.Endpoint.Cowboy2Handler.init/2
        (cowboy) /opt/imcon_x/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
        (cowboy) /opt/imcon_x/deps/cowboy/src/cowboy_stream_h.erl:296: :cowboy_stream_h.execute/3
        (cowboy) /opt/imcon_x/deps/cowboy/src/cowboy_stream_h.erl:274: :cowboy_stream_h.request_process/3
        (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

defmodule Imcon.Auth.User do
  
  use Ecto.Schema

  alias Imcon.Thorn.{Tree, UserTree}
  alias Imcon.Chat.{Channel, ChannelUser}

  @derive {Poison.Encoder, only: [:id, :first_name, :last_name, :email, :is_admin]}

  schema "user" do
    
    field :first_name, :string
    field :last_name, :string
    field :email, :string
    field :role, :string, default: "user"
    field :encrypted_password, :string
    field :password, :string, virtual: true

    has_many :owned_tree, Tree
    has_many :user_tree, UserTree
    has_many :tree, through: [:user_tree, :tree]
    many_to_many :channel, Channel, join_through: ChannelUser
    has_many :channel_user, ChannelUser

    timestamps()
  end
end
defmodule ImconWeb.ChannelController do
  use ImconWeb, :controller

  alias ImconWeb.{ChannelView, ChannelUserService, UnreadService, EventChannel, ChangesetView}
  alias Imcon.Chat
  alias Imcon.Chat.Channel

  plug(Guardian.Plug.EnsureAuthenticated)
  plug :scrub_params, "channel" when action in [:create, :update]

  def index(conn, _params) do

    channels = Repo.all(Chat.public)
    
    joined_status = ChannelUserService.joined_channels_status(Guardian.Plug.current_resource(conn))
    render(conn, "index.json", channels: channels, joined_status: joined_status)
  end

  def create(conn, %{"channel" => channel_params}) do

    case ChannelUserService.insert_channel(channel_params, Guardian.Plug.current_resource(conn)) do
      {:ok, channel} ->
        payload = ChannelView.render("show.json", channel: channel, joined: false)
        notify_channel_created(payload)
        conn
        |> put_status(:created)
        |> json(Map.put(payload, :joined, true))
      {:error, changeset} ->
        conn
        |> put_status(:bad_request)
        |> render(ChangesetView, :message, changeset: changeset)
    end
  end

  def read(conn, %{"channel_id" => channel_id, "ts" => ts}) do
    channel = Repo.get(Channel, channel_id)

    case UnreadService.mark_read(Guardian.Plug.current_resource(conn), channel, ts) do
      {:ok, _struct} ->
        conn
        |> put_status(:ok)
        |> json(%{})
      {:error, message} ->
        conn
        |> put_status(:bad_request)
        |> json(%{message: message})
    end
  end

  defp notify_channel_created(payload) do
    EventChannel.push_out("channel_created", payload)
  end
end
defmodule ImconWeb.ChannelUserService do
  use ImconWeb, :service

  alias Imcon.Time
  alias Imcon.Chat
  alias Imcon.Chat.{Channel, ChannelUser, UserReadMessage}
  alias Imcon.Auth.User

  def insert_channel(params, user) do
    Repo.transaction(fn ->
      changeset = Chat.public_changeset(%Channel{}, params)
      case Repo.insert(changeset) do
        {:ok, channel} ->
          create_channel_user(channel, user)
          channel
        {:error, changeset} ->
          Repo.rollback(changeset)
      end
    end)
  end

  def joined_channels_status(user) do
    # TODO: only channel_user of joined channels are needed
    channel_user = Repo.all assoc(user, :channel_user)
    Enum.reduce(channel_user, %{}, fn(x, acc) -> Map.put(acc, x.channel_id, true) end)
  end

  def create_channel_user(channel, user, options \\ []) do
    joined_at = Keyword.get(options, :joined_at, Time.now_datetime)
    Repo.transaction(fn ->
      params = %{channel_id: channel.id, user_id: user.id}
      Repo.insert!(Chat.channel_user_changeset(%ChannelUser{}, Map.put(params, :joined_at, joined_at)))
      # Use now datetime is OK, not necessary to use datetime of channel's latest message
      Repo.insert!(Chat.user_read_changeset(%UserReadMessage{}, Map.put(params, :latest_ts, Time.now_datetime)))
    end)
  end

  def join_default_channels(user) do
    names = Application.get_env(:imcon, Channel)[:default_channels]
    channels = Repo.all(from ch in Channel, where: ch.name in ^names)
    Enum.each channels, fn (channel) ->
      create_channel_user(channel, user)
    end
  end

  def rejoin_channel(%User{} = user, channel) do
    rejoin_channel(user.id, channel)
  end
  def rejoin_channel(user_id, channel) when is_integer(user_id) do
    channel_user = Repo.get_by ChannelUser, user_id: user_id, channel_id: channel.id
    cond do
      channel_user &amp;&amp; !channel_user.joined_at ->
        changeset = Ecto.Changeset.change(channel_user, joined_at: Time.now_datetime)
        Repo.update changeset
      channel_user &amp;&amp; channel_user.joined_at ->
        {:ok, channel_user}
      !channel_user ->
        raise "Там нет никаких взаимоотношений #{inspect channel} user##{user_id}"
    end
  end

  def direct_channels_user(%User{id: user_id}) do
    result = Repo.all(from ch in Chat.direct, join: cu in ChannelUser, on: ch.id == cu.channel_id,
                                        where: cu.user_id == ^user_id, select: {ch, cu})
    Enum.unzip(result)
  end

  def join_direct_channel(user, other_user) do
    name = Chat.direct_name(user.id, other_user.id)
    channel = Repo.get_by(Channel, name: name)
    # TODO: refactor
    if channel do
      case rejoin_channel(user, channel) do
        {:ok, _} -> {:ok, channel, :rejoin}
        other    -> other
      end
    else
      case create_direct_channel_for(user, other_user) do
        {:ok, channel} -> {:ok, channel, :new}
        other          -> other
      end
    end
  end

  def create_direct_channel_for(user, other_user) do
    channel_changeset = Chat.direct_changeset %Channel{}, %{name: Chat.direct_name(user.id, other_user.id)}
    Repo.transaction(fn ->
      channel = Repo.insert!(channel_changeset)
      create_channel_user(channel, user)
      create_channel_user(channel, other_user, joined_at: nil)
      channel
    end)
  end
end

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.