Cleanup work
This commit is contained in:
parent
eb6631dd8b
commit
f0cf03141b
11 changed files with 59 additions and 9 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -33,6 +33,7 @@ birdy_chat-*.tar
|
||||||
|
|
||||||
# Ignore messages folder
|
# Ignore messages folder
|
||||||
/priv/messages/
|
/priv/messages/
|
||||||
|
!priv/messages/README.md
|
||||||
|
|
||||||
# In case you use Node.js/npm, you want to ignore these.
|
# In case you use Node.js/npm, you want to ignore these.
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ if System.get_env("PHX_SERVER") do
|
||||||
end
|
end
|
||||||
|
|
||||||
config :birdy_chat, BirdyChatWeb.Endpoint,
|
config :birdy_chat, BirdyChatWeb.Endpoint,
|
||||||
http: [port: String.to_integer(System.get_env("PORT", "4000"))]
|
http: [port: String.to_integer(System.get_env("BIRDY_CHAT_PORT", "4000"))]
|
||||||
|
|
||||||
if config_env() == :prod do
|
if config_env() == :prod do
|
||||||
# The secret key base is used to sign/encrypt cookies and other secrets.
|
# The secret key base is used to sign/encrypt cookies and other secrets.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,18 @@
|
||||||
defmodule BirdyChat.Dispatcher do
|
defmodule BirdyChat.Dispatcher do
|
||||||
|
@moduledoc """
|
||||||
|
Main dispatcher of messages - decides to either write them to local file system or send them via
|
||||||
|
HTTP to peers.
|
||||||
|
|
||||||
|
It originally started as a websocket connection between servers, but then I decided to rip
|
||||||
|
it out and replace with simple HTTP request-response for the following reasons:
|
||||||
|
|
||||||
|
1. HTTP guarantees immediate feedback (request succeeds or not), making the addition of caching
|
||||||
|
or retries easy.
|
||||||
|
2. HTTP requests have well-know semantic so I can i.e use HTTP statuses for error signals
|
||||||
|
instead of inventing my own error language.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@spec dispatch(Ecto.Changeset.t()) :: :ok | {:error, String.t()}
|
||||||
def dispatch(%Ecto.Changeset{changes: changes} = changeset) do
|
def dispatch(%Ecto.Changeset{changes: changes} = changeset) do
|
||||||
case changes do
|
case changes do
|
||||||
%{routing: :local} -> BirdyChat.MessageWriter.write(changeset.changes)
|
%{routing: :local} -> BirdyChat.MessageWriter.write(changeset.changes)
|
||||||
|
|
@ -6,13 +20,13 @@ defmodule BirdyChat.Dispatcher do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_to_remote(%{server: server, to: to} = message) do
|
defp send_to_remote(%{server: server, from: from} = message) do
|
||||||
{_name, base_url} =
|
{name, base_url} =
|
||||||
BirdyChat.Identity.peers()
|
BirdyChat.Identity.peers()
|
||||||
|> Enum.find(fn {name, _url} -> name == server end)
|
|> Enum.find(fn {name, _url} -> name == server end)
|
||||||
|
|
||||||
api_url = base_url <> "/api/internal"
|
api_url = base_url <> "/api/internal"
|
||||||
token = Phoenix.Token.sign(BirdyChatWeb.Endpoint, "serverAuth", to)
|
token = Phoenix.Token.sign(BirdyChatWeb.Endpoint, "serverAuth", from)
|
||||||
|
|
||||||
{_request, result} =
|
{_request, result} =
|
||||||
Req.new(url: api_url, retry: false, method: :post)
|
Req.new(url: api_url, retry: false, method: :post)
|
||||||
|
|
@ -24,6 +38,12 @@ defmodule BirdyChat.Dispatcher do
|
||||||
# Handle more when you encounter errors
|
# Handle more when you encounter errors
|
||||||
case result do
|
case result do
|
||||||
%Req.Response{status: 201} -> :ok
|
%Req.Response{status: 201} -> :ok
|
||||||
|
# This should never happen under normal circumstances so I am commenting this out but maybe
|
||||||
|
# needs a second look.
|
||||||
|
# %Req.Response{status: 403} -> {:error, "Unauthorised"}
|
||||||
|
|
||||||
|
# Peer is down.
|
||||||
|
%Req.TransportError{reason: :econnrefused} -> {:error, "peer #{name} is unreachable"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,12 @@ defmodule BirdyChat.Identity do
|
||||||
peers: %{"test2" => "http://localhost:4001"},
|
peers: %{"test2" => "http://localhost:4001"},
|
||||||
mode: :test
|
mode: :test
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{identity, peers} ->
|
||||||
|
peers = parse_peers(peers)
|
||||||
|
identity = parse_identity(identity)
|
||||||
|
|
||||||
|
%__MODULE__{identity: identity, peers: peers, mode: :connected}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
defmodule BirdyChat.MessageWriter do
|
defmodule BirdyChat.MessageWriter do
|
||||||
@moduledoc false
|
@moduledoc """
|
||||||
|
Simple file writer that stores messages in priv folder of Elixir application/release.
|
||||||
|
"""
|
||||||
|
|
||||||
@spec write(%{to: String.t(), from: String.t(), message: String.t()}) :: :ok
|
@spec write(%{to: String.t(), from: String.t(), message: String.t()}) :: :ok
|
||||||
def write(message) do
|
def write(message) do
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@ defmodule BirdyChatWeb.Api.Messages.Controller do
|
||||||
|> put_status(:created)
|
|> put_status(:created)
|
||||||
|> render(:create, message: changeset.changes)
|
|> render(:create, message: changeset.changes)
|
||||||
|
|
||||||
:error ->
|
{:error, error} ->
|
||||||
conn
|
conn
|
||||||
|> put_status(:unprocessable_entity)
|
|> put_status(:unprocessable_entity)
|
||||||
|> render(:error, changeset: changeset)
|
|> render(:error, message: error)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:error, changeset} ->
|
{:error, changeset} ->
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@ defmodule BirdyChatWeb.Api.Messages.JSON do
|
||||||
message
|
message
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render("error.json", %{message: message}) do
|
||||||
|
%{errors: %{"general" => Gettext.dgettext(BirdyChatWeb.Gettext, "errors", message, [])}}
|
||||||
|
end
|
||||||
|
|
||||||
def render("error.json", %{changeset: changeset}) do
|
def render("error.json", %{changeset: changeset}) do
|
||||||
errors = Ecto.Changeset.traverse_errors(changeset, &get_error/1)
|
errors = Ecto.Changeset.traverse_errors(changeset, &get_error/1)
|
||||||
%{errors: errors}
|
%{errors: errors}
|
||||||
|
|
|
||||||
6
mix.exs
6
mix.exs
|
|
@ -63,7 +63,10 @@ defmodule BirdyChat.MixProject do
|
||||||
{:jason, "~> 1.2"},
|
{:jason, "~> 1.2"},
|
||||||
{:dns_cluster, "~> 0.2.0"},
|
{:dns_cluster, "~> 0.2.0"},
|
||||||
{:bandit, "~> 1.5"},
|
{:bandit, "~> 1.5"},
|
||||||
{:credo, "~> 1.0", only: [:dev, :test]},
|
|
||||||
|
# Static analysis tools
|
||||||
|
{:credo, "~> 1.0", only: [:dev, :test], runtime: false},
|
||||||
|
{:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false},
|
||||||
|
|
||||||
# Telemetry
|
# Telemetry
|
||||||
{:opentelemetry, "~> 1.0"},
|
{:opentelemetry, "~> 1.0"},
|
||||||
|
|
@ -90,6 +93,7 @@ defmodule BirdyChat.MixProject do
|
||||||
precommit: [
|
precommit: [
|
||||||
"compile --warnings-as-errors",
|
"compile --warnings-as-errors",
|
||||||
"credo --strict",
|
"credo --strict",
|
||||||
|
"dialyzer",
|
||||||
"deps.unlock --unused",
|
"deps.unlock --unused",
|
||||||
"format",
|
"format",
|
||||||
"test"
|
"test"
|
||||||
|
|
|
||||||
2
mix.lock
2
mix.lock
|
|
@ -7,9 +7,11 @@
|
||||||
"credo": {:hex, :credo, "1.7.16", "a9f1389d13d19c631cb123c77a813dbf16449a2aebf602f590defa08953309d4", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0562af33756b21f248f066a9119e3890722031b6d199f22e3cf95550e4f1579"},
|
"credo": {:hex, :credo, "1.7.16", "a9f1389d13d19c631cb123c77a813dbf16449a2aebf602f590defa08953309d4", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0562af33756b21f248f066a9119e3890722031b6d199f22e3cf95550e4f1579"},
|
||||||
"ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"},
|
"ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"},
|
||||||
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
|
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
|
||||||
|
"dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"},
|
||||||
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
|
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
|
||||||
"ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"},
|
"ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"},
|
||||||
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
|
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
|
||||||
|
"erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"},
|
||||||
"esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
|
"esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
|
||||||
"expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"},
|
"expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"},
|
||||||
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
|
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
|
||||||
|
|
|
||||||
1
priv/messages/README.md
Normal file
1
priv/messages/README.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
This folder is used for storing messages local to this server. The contents are otherwise ignored.
|
||||||
|
|
@ -15,6 +15,16 @@ defmodule BirdyChatWeb.Api.MessagesTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "POST /api/messages to other server" do
|
describe "POST /api/messages to other server" do
|
||||||
|
test "returns error when the peer is down", %{conn: conn, url: url} do
|
||||||
|
message = %{from: "test1-user", to: "test2-user", message: "123"}
|
||||||
|
Req.Test.expect(BirdyChat.Dispatcher, &Req.Test.transport_error(&1, :econnrefused))
|
||||||
|
|
||||||
|
payload = Jason.encode!(message)
|
||||||
|
conn = post(conn, url, payload)
|
||||||
|
assert result = json_response(conn, :unprocessable_entity)
|
||||||
|
assert result == %{"errors" => %{"general" => "peer test2 is unreachable"}}
|
||||||
|
end
|
||||||
|
|
||||||
test "returns error when a peer is unknown", %{conn: conn, url: url} do
|
test "returns error when a peer is unknown", %{conn: conn, url: url} do
|
||||||
message = %{from: "test1-user", to: "fake-user", message: "123"}
|
message = %{from: "test1-user", to: "fake-user", message: "123"}
|
||||||
|
|
||||||
|
|
@ -50,7 +60,7 @@ defmodule BirdyChatWeb.Api.MessagesTest do
|
||||||
{"authorization", token} =
|
{"authorization", token} =
|
||||||
Enum.find(conn.req_headers, fn {key, _v} -> key == "authorization" end)
|
Enum.find(conn.req_headers, fn {key, _v} -> key == "authorization" end)
|
||||||
|
|
||||||
{:ok, "test2-user"} =
|
{:ok, "test1-user"} =
|
||||||
Phoenix.Token.verify(BirdyChatWeb.Endpoint, "serverAuth", token, max_age: 1200)
|
Phoenix.Token.verify(BirdyChatWeb.Endpoint, "serverAuth", token, max_age: 1200)
|
||||||
|
|
||||||
assert conn.body_params == expected_body_params
|
assert conn.body_params == expected_body_params
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue