Preparatory work
This commit is contained in:
parent
a22ba724df
commit
386a331956
9 changed files with 217 additions and 1 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -31,7 +31,9 @@ birdy_chat-*.tar
|
||||||
# Ignore digested assets cache.
|
# Ignore digested assets cache.
|
||||||
/priv/static/cache_manifest.json
|
/priv/static/cache_manifest.json
|
||||||
|
|
||||||
|
# Ignore messages folder
|
||||||
|
/priv/messages/
|
||||||
|
|
||||||
# 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
|
||||||
/assets/node_modules/
|
/assets/node_modules/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,14 @@ defmodule BirdyChat.Application do
|
||||||
|
|
||||||
use Application
|
use Application
|
||||||
|
|
||||||
|
@env Mix.env
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
children = [
|
children = [
|
||||||
BirdyChatWeb.Telemetry,
|
BirdyChatWeb.Telemetry,
|
||||||
BirdyChat.Repo,
|
BirdyChat.Repo,
|
||||||
|
{BirdyChat.Identity, env: @env},
|
||||||
{DNSCluster, query: Application.get_env(:birdy_chat, :dns_cluster_query) || :ignore},
|
{DNSCluster, query: Application.get_env(:birdy_chat, :dns_cluster_query) || :ignore},
|
||||||
{Phoenix.PubSub, name: BirdyChat.PubSub},
|
{Phoenix.PubSub, name: BirdyChat.PubSub},
|
||||||
# Start a worker by calling: BirdyChat.Worker.start_link(arg)
|
# Start a worker by calling: BirdyChat.Worker.start_link(arg)
|
||||||
|
|
|
||||||
56
lib/birdy_chat/identity.ex
Normal file
56
lib/birdy_chat/identity.ex
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
defmodule BirdyChat.Identity do
|
||||||
|
use Agent
|
||||||
|
|
||||||
|
defstruct [:identity, :peers, :mode]
|
||||||
|
|
||||||
|
def start_link(opts) do
|
||||||
|
name = opts[:name] || __MODULE__
|
||||||
|
input = opts[:input]
|
||||||
|
|
||||||
|
Agent.start_link(fn -> initialise(input) end, name: name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def mode(pid \\ __MODULE__) do
|
||||||
|
Agent.get(pid, & &1.mode)
|
||||||
|
end
|
||||||
|
|
||||||
|
def identity(pid \\ __MODULE__) do
|
||||||
|
Agent.get(pid, & &1.identity)
|
||||||
|
end
|
||||||
|
|
||||||
|
def peers(pid \\ __MODULE__) do
|
||||||
|
Agent.get(pid, & &1.peers)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_identity(value) do
|
||||||
|
case Integer.parse(value) do
|
||||||
|
{_number, ""} -> value
|
||||||
|
_ -> raise "Identity must be a string that can be converted to Integer, got: >>#{value}<<."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_peers(peers) do
|
||||||
|
values = String.split(peers, ";")
|
||||||
|
|
||||||
|
for value <- values,
|
||||||
|
[identity, address] = String.split(value, "::"), into: %{} do
|
||||||
|
{identity, address}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialise(%{peers: peers, identity: identity}) do
|
||||||
|
peers = parse_peers(peers)
|
||||||
|
identity = parse_identity(identity)
|
||||||
|
|
||||||
|
%__MODULE__{identity: identity, peers: peers, mode: :connected}
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialise(nil) do
|
||||||
|
identity = System.get_env("BIRDY_CHAT_IDENTITY")
|
||||||
|
peers = System.get_env("BIRDY_CHAT_PEERS")
|
||||||
|
|
||||||
|
case {identity, peers} do
|
||||||
|
{nil, nil} -> %__MODULE__{identity: "test", peers: [], mode: :singleton}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
46
lib/birdy_chat/message.ex
Normal file
46
lib/birdy_chat/message.ex
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
defmodule BirdyChat.Message do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field :from, :string
|
||||||
|
field :to, :string
|
||||||
|
field :message, :string
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate(params) do
|
||||||
|
changeset =
|
||||||
|
%__MODULE__{}
|
||||||
|
|> Ecto.Changeset.cast(params, [:from, :to, :message])
|
||||||
|
|> Ecto.Changeset.validate_required([:from, :to, :message])
|
||||||
|
|
||||||
|
if changeset.valid? do
|
||||||
|
{:ok, changeset}
|
||||||
|
else
|
||||||
|
{:error, changeset}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def write(%{to: to, from: from, message: message} = msg) do
|
||||||
|
message_to_write = "#{from}: #{message}\n"
|
||||||
|
:ok = create_messages_folder!(Application.app_dir(:birdy_chat, ["priv", "messages"]))
|
||||||
|
path = Application.app_dir(:birdy_chat, ["priv", "messages", "#{to}.txt"])
|
||||||
|
result = File.write!(path, message_to_write, [:append])
|
||||||
|
{:ok, msg}
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_messages_folder!(path) do
|
||||||
|
case File.mkdir(path) do
|
||||||
|
:ok ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
{:error, :eexist} ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
raise File.Error,
|
||||||
|
reason: reason,
|
||||||
|
action: "make directory",
|
||||||
|
path: IO.chardata_to_string(path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
20
lib/birdy_chat_web/api/messages/controller.ex
Normal file
20
lib/birdy_chat_web/api/messages/controller.ex
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
defmodule BirdyChatWeb.Api.Messages.Controller do
|
||||||
|
use BirdyChatWeb, :controller
|
||||||
|
|
||||||
|
def create(conn, params) do
|
||||||
|
case BirdyChat.Message.validate(params) do
|
||||||
|
{:ok, changeset} ->
|
||||||
|
case BirdyChat.Message.write(changeset.changes) do
|
||||||
|
{:ok, msg} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:created)
|
||||||
|
|> render(:create, message: msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, changeset} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:unprocessable_entity)
|
||||||
|
|> render(:error, changeset: changeset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
14
lib/birdy_chat_web/api/messages/json.ex
Normal file
14
lib/birdy_chat_web/api/messages/json.ex
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
defmodule BirdyChatWeb.Api.Messages.JSON do
|
||||||
|
def render("create.json", %{message: message}) do
|
||||||
|
message
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("error.json", %{changeset: changeset}) do
|
||||||
|
errors = Ecto.Changeset.traverse_errors(changeset, &get_error/1)
|
||||||
|
%{errors: errors}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_error({msg, opts}) do
|
||||||
|
Gettext.dgettext(BirdyChatWeb.Gettext, "errors", msg, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -20,6 +20,12 @@ defmodule BirdyChatWeb.Router do
|
||||||
get "/", PageController, :home
|
get "/", PageController, :home
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scope "/api", BirdyChatWeb.Api do
|
||||||
|
pipe_through [:api]
|
||||||
|
|
||||||
|
post "/messages", Messages.Controller, :create
|
||||||
|
end
|
||||||
|
|
||||||
# Other scopes may use custom stacks.
|
# Other scopes may use custom stacks.
|
||||||
# scope "/api", BirdyChatWeb do
|
# scope "/api", BirdyChatWeb do
|
||||||
# pipe_through :api
|
# pipe_through :api
|
||||||
|
|
|
||||||
29
test/birdy_chat/identity_test.exs
Normal file
29
test/birdy_chat/identity_test.exs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
defmodule BirdyChat.IdentityTest do
|
||||||
|
use BirdyChat.DataCase
|
||||||
|
|
||||||
|
setup do
|
||||||
|
registry = start_supervised!({Registry, keys: :unique, name: __MODULE__})
|
||||||
|
%{registry: registry}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "process" do
|
||||||
|
test "can be started with a new name" do
|
||||||
|
name = {:via, Registry, {__MODULE__, "identity"}}
|
||||||
|
{:ok, process} = BirdyChat.Identity.start_link(name: name)
|
||||||
|
mode = BirdyChat.Identity.mode(process)
|
||||||
|
assert mode == :singleton
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can be started by giving values like environment variables" do
|
||||||
|
name = {:via, Registry, {__MODULE__, "identity"}}
|
||||||
|
input = %{peers: "13::http://localhost:4001;14::http://localhost:4002", identity: "12"}
|
||||||
|
{:ok, process} = BirdyChat.Identity.start_link(name: name, input: input)
|
||||||
|
|
||||||
|
identity = BirdyChat.Identity.identity(process)
|
||||||
|
assert identity == "12"
|
||||||
|
|
||||||
|
peers = BirdyChat.Identity.peers(process)
|
||||||
|
assert peers == %{"13" => "http://localhost:4001", "14" => "http://localhost:4002"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
40
test/birdy_chat_web/api/messages_test.exs
Normal file
40
test/birdy_chat_web/api/messages_test.exs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
defmodule BirdyChatWeb.Api.MessagesTest do
|
||||||
|
use BirdyChatWeb.ConnCase
|
||||||
|
|
||||||
|
setup %{conn: conn} do
|
||||||
|
url = ~p"/api/messages"
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|
||||||
|
%{conn: conn, url: url}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "POST /api/messages" do
|
||||||
|
test "returns errors for invalid message", %{conn: conn, url: url} do
|
||||||
|
payload = Jason.encode!(%{})
|
||||||
|
conn = post(conn, url, payload)
|
||||||
|
assert result = json_response(conn, :unprocessable_entity)
|
||||||
|
|
||||||
|
expected_result = %{
|
||||||
|
"errors" => %{
|
||||||
|
"from" => ["can't be blank"],
|
||||||
|
"message" => ["can't be blank"],
|
||||||
|
"to" => ["can't be blank"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert result == expected_result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns message and ok when successful", %{conn: conn, url: url} do
|
||||||
|
message = %{"from" => "2-user", "to" => "1-user", "message" => "123"}
|
||||||
|
payload = Jason.encode!(message)
|
||||||
|
conn = post(conn, url, payload)
|
||||||
|
assert result = json_response(conn, :created)
|
||||||
|
|
||||||
|
assert result == message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue