defmodule Mix.Tasks.Phoenix.Gen.Instachat do
# This module is a copy paste of Gen.Channel with a few additions
use Mix.Task
import Mix.Generator
import Phoenix.Gen.Utils
@shortdoc "Generate an instant chatroom for a Phoenix Application"
@moduledoc """
Generates a zero configuration chat room
mix phoenix.gen.instachat
## Command line options
## Examples
mix phoenix.gen.instachat
"""
def run(_opts) do
channel_name = "instachat"
bindings = [
app_name: app_name_camel,
module_name: Mix.Utils.camelize(channel_name)<>"Channel",
channel_name: channel_name
]
# Add the Channel
channel_file = Path.join channels_path, "#{channel_name}_channel.ex"
create_file channel_file, channel_template(bindings)
# Add the Channel Route
route_file = Path.join ~w|web router.ex|
route_contents = File.read! route_file
[_ | captures] = Regex.run(~r/(.*defmodule.*)(\nend)/s, route_contents)
route_contents = Enum.join captures, channel_route_template(bindings)
File.write! route_file, route_contents
Mix.Shell.IO.info "A route was added for this channel."
# Generate the Controller and Action
Mix.Tasks.Phoenix.Gen.Controller.run ["instachat", "index"]
# Overwrite the Template
template_path = Path.join [templates_path, "instachat", "index.html.eex"]
File.write! template_path, html_template([])
# Add the Config
config_path = Path.join ~w|config config.exs|
File.write! config_path, config_template([]), [:append]
Mix.Shell.IO.info "Your config/config.exs was edited."
Mix.Shell.IO.info "An ets table will be started to manage nicknames.\n"
# For some reason router changes don't trigger a recompile
# so we must manually clean the projec
Mix.Tasks.Clean.run []
Mix.Shell.IO.info """
A chat room has been generated.
Run: mix phoenix.server
Visit: http://localhost:4000/instachat
It takes a minute for the chat to start working, I'm not sure why. :/
"""
end
embed_template :channel_route, """
socket "/instachatsocket", <%= @app_name %> do
channel "instachat:*", InstachatChannel
end
"""
embed_template :config, """
# Start an ets table to manage chat nick names
:ets.new(:instachat, [:named_table, :public, :set])
"""
embed_template :channel, """
defmodule <%= @app_name %>.<%= @module_name %> do
use Phoenix.Channel
@doc "Handles when a user first joins the channel."
def join(topic, nick, socket) do
case :ets.lookup(:instachat, nick) do
[{^nick, _}] ->
reply socket, "error", %{nickTaken: true}
{:error, :nick_taken, socket}
_ ->
socket = Phoenix.Socket.put_topic(socket, topic)
socket = Phoenix.Socket.assign(socket, :nick, nick)
:ets.insert(:instachat, {nick, socket[:pid]})
reply socket, topic, %{start: "Welcome to the chat!"}
end
end
@doc "Handles a user sending a chat message"
def handle_in(topic, %{"body" => body}, socket) do
broadcast socket, topic,
%{body: body, nick: socket.assigns[:nick], room: socket.assigns[:room]}
end
@doc "Handles a user joining a room"
def handle_in(topic, %{"room" => room}, socket) do
socket = Phoenix.Socket.assign(socket, :room, room)
broadcast socket, topic, %{join: socket.assigns[:nick], room: room}
reply socket, topic, %{entered: room}
end
@doc "Handles outgoing broadcasts of chat messages"
def handle_out(topic, %{body: body, nick: nick, room: room}, socket) do
cond do
# don't send the message to people not in the room
socket.assigns[:room] !== room -> {:ok, socket}
# send it to everyone else
true -> reply socket,
topic, %{body: body, nick: nick}
end
end
@doc "Handles outgoing broadcasts of room joins"
def handle_out(topic, %{join: nick, room: room}, socket) do
cond do
# don't send the join message to people not in the room
socket.assigns[:room] !== room -> {:ok, socket}
# don't send the join message to the person who joined
socket.assigns[:nick] == nick -> {:ok, socket}
# send it to everyone else
true -> reply socket,
topic, %{join: nick}
end
end
@doc "Catchall for broadcasts, anything not handlede gets forwarded"
def handle_out(topic, msg, socket) do
reply socket, topic, msg
end
@doc "Remove nicknames when a user disconnects"
def leave(_, socket) do
:ets.delete(:instachat, socket.assigns[:nick])
{:ok, socket}
end
end
"""
embed_template :html, """