Skip to content

Commit 8d55213

Browse files
authored
Merge pull request #1301 from code-corps/1293-accomodating-messages-api-to-match-client
Add support for creation of associated conversations with message via params
2 parents 44e53c1 + 7546651 commit 8d55213

File tree

20 files changed

+514
-31
lines changed

20 files changed

+514
-31
lines changed

config/dev.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ config :code_corps, CodeCorps.Repo,
4343
pool_size: 10
4444

4545
# CORS allowed origins
46-
config :code_corps, allowed_origins: ["http://localhost:4200"]
46+
config :code_corps, allowed_origins: ["http://localhost:4200", "chrome-extension://pfdhoblngboilpfeibdedpjgfnlcodoo"]
4747

4848
config :code_corps, CodeCorps.Guardian,
4949
secret_key: "e62fb6e2746f6b1bf8b5b735ba816c2eae1d5d76e64f18f3fc647e308b0c159e"

lib/code_corps/messages/conversation_parts.ex

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,21 @@ defmodule CodeCorps.Messages.ConversationParts do
1010
ConversationPart,
1111
Repo
1212
}
13+
alias CodeCorpsWeb.ConversationChannel
1314

1415
@spec create(map) :: ConversationPart.t | Ecto.Changeset.t
1516
def create(attrs) do
16-
%ConversationPart{} |> create_changeset(attrs) |> Repo.insert()
17+
with {:ok, %ConversationPart{} = conversation_part} <- %ConversationPart{} |> create_changeset(attrs) |> Repo.insert() do
18+
ConversationChannel.broadcast_new_conversation_part(conversation_part)
19+
{:ok, conversation_part}
20+
end
1721
end
1822

1923
@doc false
2024
@spec create_changeset(ConversationPart.t, map) :: Ecto.Changeset.t
2125
def create_changeset(%ConversationPart{} = conversation_part, attrs) do
2226
conversation_part
23-
|> cast(attrs, [:author_id, :body, :conversation_id, :read_at])
27+
|> cast(attrs, [:author_id, :body, :conversation_id])
2428
|> validate_required([:author_id, :body, :conversation_id])
2529
|> assoc_constraint(:author)
2630
|> assoc_constraint(:conversation)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
defmodule CodeCorps.Messages.Conversations do
2+
@moduledoc ~S"""
3+
Subcontext aimed at managing `CodeCorps.Conversation` records aimed at a
4+
specific user belonging to a `CodeCorps.Message`.
5+
"""
6+
7+
alias Ecto.Changeset
8+
9+
alias CodeCorps.{Conversation}
10+
11+
@doc false
12+
@spec create_changeset(Conversation.t, map) :: Ecto.Changeset.t
13+
def create_changeset(%Conversation{} = conversation, %{} = attrs) do
14+
conversation
15+
|> Changeset.cast(attrs, [:user_id])
16+
|> Changeset.validate_required([:user_id])
17+
|> Changeset.assoc_constraint(:user)
18+
end
19+
end

lib/code_corps/messages/messages.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ defmodule CodeCorps.Messages do
6767
def create(%{} = params) do
6868
%Message{}
6969
|> Message.changeset(params)
70+
|> Changeset.cast(params, [:author_id, :project_id])
71+
|> Changeset.validate_required([:author_id, :project_id])
72+
|> Changeset.assoc_constraint(:author)
73+
|> Changeset.assoc_constraint(:project)
74+
|> Changeset.cast_assoc(:conversations, with: &Messages.Conversations.create_changeset/2)
7075
|> Repo.insert()
7176
end
7277

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
defmodule CodeCorpsWeb.ConversationChannel do
2+
use Phoenix.Channel
3+
4+
alias CodeCorps.{Conversation, Policy, Repo, User}
5+
alias Phoenix.Socket
6+
7+
@spec join(String.t, map, Socket.t) :: {:ok, Socket.t} | {:error, map}
8+
def join("conversation:" <> id, %{}, %Socket{} = socket) do
9+
with %Conversation{} = conversation <- Conversation |> Repo.get(id),
10+
%User{} = current_user <- socket.assigns[:current_user],
11+
{:ok, :authorized} <- current_user |> Policy.authorize(:show, conversation, %{}) do
12+
13+
{:ok, socket}
14+
else
15+
nil -> {:error, %{reason: "unauthenticated"}}
16+
{:error, :not_authorized} -> {:error, %{reason: "unauthorized"}}
17+
end
18+
end
19+
20+
def event("new:conversation-part", socket, message) do
21+
broadcast socket, "new:conversation-part", message
22+
{:ok, socket}
23+
end
24+
25+
def broadcast_new_conversation_part(conversation_part) do
26+
channel = "conversation:#{conversation_part.conversation_id}"
27+
event = "new:conversation-part"
28+
payload = %{
29+
id: conversation_part.id
30+
}
31+
32+
CodeCorpsWeb.Endpoint.broadcast(channel, event, payload)
33+
end
34+
end

lib/code_corps_web/channels/user_socket.ex

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule CodeCorpsWeb.UserSocket do
22
use Phoenix.Socket
33

44
## Channels
5-
# channel "room:*", CodeCorps.RoomChannel
5+
channel "conversation:*", CodeCorpsWeb.ConversationChannel
66

77
## Transports
88
transport :websocket, Phoenix.Transports.WebSocket,
@@ -20,8 +20,13 @@ defmodule CodeCorpsWeb.UserSocket do
2020
#
2121
# See `Phoenix.Token` documentation for examples in
2222
# performing token verification on connect.
23-
def connect(_params, socket) do
24-
{:ok, socket}
23+
def connect(%{"token" => token}, socket) do
24+
with {:ok, claims} <- CodeCorps.Guardian.decode_and_verify(token),
25+
{:ok, user} <- CodeCorps.Guardian.resource_from_claims(claims) do
26+
{:ok, assign(socket, :current_user, user)}
27+
else
28+
_ -> {:ok, socket}
29+
end
2530
end
2631

2732
# Socket id's are topics that allow you to identify all sockets for a given user:

lib/code_corps_web/controllers/conversation_controller.ex

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@ defmodule CodeCorpsWeb.ConversationController do
1515
@spec index(Conn.t, map) :: Conn.t
1616
def index(%Conn{} = conn, %{} = params) do
1717
with %User{} = current_user <- conn |> CodeCorps.Guardian.Plug.current_resource,
18-
conversations <- Conversation |> Policy.scope(current_user) |> Messages.list_conversations(params) do
18+
conversations <- Conversation |> Policy.scope(current_user) |> Messages.list_conversations(params) |> preload() do
1919
conn |> render("index.json-api", data: conversations)
2020
end
2121
end
2222

2323
@spec show(Conn.t, map) :: Conn.t
2424
def show(%Conn{} = conn, %{"id" => id}) do
2525
with %User{} = current_user <- conn |> CodeCorps.Guardian.Plug.current_resource,
26-
%Conversation{} = conversation <- Messages.get_conversation(id),
26+
%Conversation{} = conversation <- Messages.get_conversation(id) |> preload(),
2727
{:ok, :authorized} <- current_user |> Policy.authorize(:show, conversation, %{}) do
2828
conn |> render("show.json-api", data: conversation)
2929
end
3030
end
31+
32+
@preloads [:conversation_parts, :message, :user]
33+
34+
def preload(data) do
35+
Repo.preload(data, @preloads)
36+
end
3137
end

lib/code_corps_web/controllers/message_controller.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,21 @@ defmodule CodeCorpsWeb.MessageController do
99
}
1010

1111
action_fallback CodeCorpsWeb.FallbackController
12-
plug CodeCorpsWeb.Plug.DataToAttributes
12+
plug CodeCorpsWeb.Plug.DataToAttributes, [includes_many: ~w(conversation)]
1313
plug CodeCorpsWeb.Plug.IdsToIntegers
1414

1515
@spec index(Conn.t, map) :: Conn.t
1616
def index(%Conn{} = conn, %{} = params) do
1717
with %User{} = current_user <- conn |> CodeCorps.Guardian.Plug.current_resource,
18-
messages <- Message |> Policy.scope(current_user) |> Messages.list(params) do
18+
messages <- Message |> Policy.scope(current_user) |> Messages.list(params) |> preload() do
1919
conn |> render("index.json-api", data: messages)
2020
end
2121
end
2222

2323
@spec show(Conn.t, map) :: Conn.t
2424
def show(%Conn{} = conn, %{"id" => id}) do
2525
with %User{} = current_user <- conn |> CodeCorps.Guardian.Plug.current_resource,
26-
%Message{} = message <- Message |> Repo.get(id),
26+
%Message{} = message <- Message |> Repo.get(id) |> preload(),
2727
{:ok, :authorized} <- current_user |> Policy.authorize(:show, message, %{}) do
2828
conn |> render("show.json-api", data: message)
2929
end
@@ -40,7 +40,7 @@ defmodule CodeCorpsWeb.MessageController do
4040
end
4141
end
4242

43-
@preloads [:author, :project]
43+
@preloads [:author, :project, :conversations]
4444

4545
def preload(data) do
4646
Repo.preload(data, @preloads)
Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
defmodule CodeCorpsWeb.Plug.DataToAttributes do
22
@moduledoc ~S"""
3-
Converts params in the JSON api "data" format into flat params convient for
3+
Converts params in the JSON api format into flat params convient for
44
changeset casting.
55
6-
This is done using `JaSerializer.Params.to_attributes/1`
6+
For base parameters, this is done using `JaSerializer.Params.to_attributes/1`
7+
8+
For included records, this is done using custom code.
79
"""
810

911
alias Plug.Conn
@@ -12,13 +14,38 @@ defmodule CodeCorpsWeb.Plug.DataToAttributes do
1214
def init(opts), do: opts
1315

1416
@spec call(Conn.t, Keyword.t) :: Plug.Conn.t
15-
def call(%Conn{params: %{"data" => data} = params} = conn, _opts) do
17+
def call(%Conn{params: %{} = params} = conn, opts \\ []) do
1618
attributes =
1719
params
1820
|> Map.delete("data")
19-
|> Map.merge(data |> JaSerializer.Params.to_attributes)
21+
|> Map.delete("included")
22+
|> Map.merge(params |> parse_data())
23+
|> Map.merge(params |> parse_included(opts))
2024

2125
conn |> Map.put(:params, attributes)
2226
end
23-
def call(%Conn{} = conn, _opts), do: conn
27+
28+
@spec parse_data(map) :: map
29+
defp parse_data(%{"data" => data}), do: data |> JaSerializer.Params.to_attributes
30+
defp parse_data(%{}), do: %{}
31+
32+
@spec parse_included(map, Keyword.t) :: map
33+
defp parse_included(%{"included" => included}, opts) do
34+
included |> Enum.reduce(%{}, fn (%{"data" => %{"type" => type}} = params, parsed) ->
35+
attributes = params |> parse_data()
36+
37+
if opts |> Keyword.get(:includes_many, []) |> Enum.member?(type) do
38+
# this is an explicitly specified has_many,
39+
# update existing data by adding new record
40+
pluralized_type = type |> Inflex.pluralize
41+
parsed |> Map.update(pluralized_type, [attributes], fn data ->
42+
data ++ [attributes]
43+
end)
44+
else
45+
# this is a belongs to, put a new submap into payload
46+
parsed |> Map.put(type, attributes)
47+
end
48+
end)
49+
end
50+
defp parse_included(%{}, _opts), do: %{}
2451
end

lib/code_corps_web/views/conversation_view.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ defmodule CodeCorpsWeb.ConversationView do
77

88
has_one :user, type: "user", field: :user_id
99
has_one :message, type: "message", field: :message_id
10+
11+
has_many :conversation_parts, serializer: CodeCorpsWeb.ConversationPartView, identifiers: :always
1012
end

0 commit comments

Comments
 (0)