HÄXFEST 2015 !!! ! ! ! !
ZZZZZZZZZ
This commit is contained in:
commit
d031961a84
37 changed files with 1165 additions and 0 deletions
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# App artifacts
|
||||||
|
/_build
|
||||||
|
/db
|
||||||
|
/deps
|
||||||
|
/*.ez
|
||||||
|
|
||||||
|
# Generate on crash by the VM
|
||||||
|
erl_crash.dump
|
||||||
|
|
||||||
|
# Static artifacts
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# Since we are building assets from web/static,
|
||||||
|
# we ignore priv/static. You may want to comment
|
||||||
|
# this depending on your deployment strategy.
|
||||||
|
/priv/static/
|
||||||
|
|
||||||
|
# The config/prod.secret.exs file by default contains sensitive
|
||||||
|
# data and you should not commit it into version control.
|
||||||
|
#
|
||||||
|
# Alternatively, you may comment the line below and commit the
|
||||||
|
# secrets file as long as you replace its contents by environment
|
||||||
|
# variables.
|
||||||
|
/config/prod.secret.exs
|
19
README.md
Normal file
19
README.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Proxichat
|
||||||
|
|
||||||
|
To start your Phoenix app:
|
||||||
|
|
||||||
|
1. Install dependencies with `mix deps.get`
|
||||||
|
2. Create and migrate your database with `mix ecto.create && mix ecto.migrate`
|
||||||
|
3. Start Phoenix endpoint with `mix phoenix.server`
|
||||||
|
|
||||||
|
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
|
||||||
|
|
||||||
|
Ready to run in production? Please [check our deployment guides](http://www.phoenixframework.org/docs/deployment).
|
||||||
|
|
||||||
|
## Learn more
|
||||||
|
|
||||||
|
* Official website: http://www.phoenixframework.org/
|
||||||
|
* Guides: http://phoenixframework.org/docs/overview
|
||||||
|
* Docs: http://hexdocs.pm/phoenix
|
||||||
|
* Mailing list: http://groups.google.com/group/phoenix-talk
|
||||||
|
* Source: https://github.com/phoenixframework/phoenix
|
69
brunch-config.js
Normal file
69
brunch-config.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
exports.config = {
|
||||||
|
// See http://brunch.io/#documentation for docs.
|
||||||
|
files: {
|
||||||
|
javascripts: {
|
||||||
|
joinTo: "js/app.js"
|
||||||
|
|
||||||
|
// To use a separate vendor.js bundle, specify two files path
|
||||||
|
// https://github.com/brunch/brunch/blob/stable/docs/config.md#files
|
||||||
|
// joinTo: {
|
||||||
|
// "js/app.js": /^(web\/static\/js)/,
|
||||||
|
// "js/vendor.js": /^(web\/static\/vendor)|(deps)/
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// To change the order of concatenation of files, explicitly mention here
|
||||||
|
// https://github.com/brunch/brunch/tree/master/docs#concatenation
|
||||||
|
// order: {
|
||||||
|
// before: [
|
||||||
|
// "web/static/vendor/js/jquery-2.1.1.js",
|
||||||
|
// "web/static/vendor/js/bootstrap.min.js"
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
stylesheets: {
|
||||||
|
joinTo: "css/app.css"
|
||||||
|
},
|
||||||
|
templates: {
|
||||||
|
joinTo: "js/app.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
conventions: {
|
||||||
|
// This option sets where we should place non-css and non-js assets in.
|
||||||
|
// By default, we set this to "/web/static/assets". Files in this directory
|
||||||
|
// will be copied to `paths.public`, which is "priv/static" by default.
|
||||||
|
assets: /^(web\/static\/assets)/
|
||||||
|
},
|
||||||
|
|
||||||
|
// Phoenix paths configuration
|
||||||
|
paths: {
|
||||||
|
// Dependencies and current project directories to watch
|
||||||
|
watched: [
|
||||||
|
"deps/phoenix/web/static",
|
||||||
|
"deps/phoenix_html/web/static",
|
||||||
|
"web/static",
|
||||||
|
"test/static"
|
||||||
|
],
|
||||||
|
|
||||||
|
// Where to compile files to
|
||||||
|
public: "priv/static"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Configure your plugins
|
||||||
|
plugins: {
|
||||||
|
babel: {
|
||||||
|
// Do not use ES6 compiler in vendor code
|
||||||
|
ignore: [/web\/static\/vendor/]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
modules: {
|
||||||
|
autoRequire: {
|
||||||
|
"js/app.js": ["web/static/js/app"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
npm: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
};
|
33
lib/proxichat.ex
Normal file
33
lib/proxichat.ex
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
defmodule Proxichat do
|
||||||
|
use Application
|
||||||
|
|
||||||
|
# See http://elixir-lang.org/docs/stable/elixir/Application.html
|
||||||
|
# for more information on OTP Applications
|
||||||
|
def start(_type, _args) do
|
||||||
|
import Supervisor.Spec, warn: false
|
||||||
|
|
||||||
|
children = [
|
||||||
|
# Start the endpoint when the application starts
|
||||||
|
supervisor(Proxichat.Endpoint, []),
|
||||||
|
# Start the Ecto repository
|
||||||
|
worker(Proxichat.Repo, []),
|
||||||
|
|
||||||
|
worker(Proxichat.ChannelAuthenticator, [[name: :proxichat_channel_auth_worker]])
|
||||||
|
|
||||||
|
# Here you could define other workers and supervisors as children
|
||||||
|
# worker(Proxichat.Worker, [arg1, arg2, arg3]),
|
||||||
|
]
|
||||||
|
|
||||||
|
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||||
|
# for other strategies and supported options
|
||||||
|
opts = [strategy: :one_for_one, name: Proxichat.Supervisor]
|
||||||
|
Supervisor.start_link(children, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Tell Phoenix to update the endpoint configuration
|
||||||
|
# whenever the application is updated.
|
||||||
|
def config_change(changed, _new, removed) do
|
||||||
|
Proxichat.Endpoint.config_change(changed, removed)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
63
lib/proxichat/channel_authenticator.ex
Normal file
63
lib/proxichat/channel_authenticator.ex
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
defmodule Proxichat.ChannelAuthenticator do
|
||||||
|
@moduledoc """
|
||||||
|
This module stores the list of current users in an ETS database and can be used to check if given user IDs are valid.
|
||||||
|
"""
|
||||||
|
use GenServer
|
||||||
|
|
||||||
|
@user_table :proxichat_users
|
||||||
|
|
||||||
|
def start_link(opts \\ []) do
|
||||||
|
GenServer.start_link(__MODULE__, :ok, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def init(:ok) do
|
||||||
|
init_db
|
||||||
|
{:ok, nil}
|
||||||
|
end
|
||||||
|
|
||||||
|
def init_db() do
|
||||||
|
if :ets.info(@user_table) == :undefined do
|
||||||
|
:ets.new @user_table, [:named_table, :set, :public, read_concurrency: true]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_user(user_id) do
|
||||||
|
case get_user user_id do
|
||||||
|
nil -> create_user
|
||||||
|
user -> user
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_user(user_id) do
|
||||||
|
case :ets.lookup @user_table, user_id do
|
||||||
|
[{_, user}] -> user.id
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_user() do
|
||||||
|
id = get_new_user_id
|
||||||
|
user = %{id: id}
|
||||||
|
update_user user
|
||||||
|
id
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_set_network(user_id, network_id) do
|
||||||
|
get_user user_id
|
||||||
|
|> Map.put(:network, network_id)
|
||||||
|
|> update_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_check_network(user_id, network_id) do
|
||||||
|
user = get_user user_id
|
||||||
|
Map.get(user, :network, nil) == network_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_user(user) do
|
||||||
|
:ets.insert @user_table, {user.id, user}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_new_user_id() do
|
||||||
|
UUID.uuid4
|
||||||
|
end
|
||||||
|
end
|
39
lib/proxichat/endpoint.ex
Normal file
39
lib/proxichat/endpoint.ex
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
defmodule Proxichat.Endpoint do
|
||||||
|
use Phoenix.Endpoint, otp_app: :proxichat
|
||||||
|
|
||||||
|
socket "/socket", Proxichat.UserSocket
|
||||||
|
|
||||||
|
# Serve at "/" the static files from "priv/static" directory.
|
||||||
|
#
|
||||||
|
# You should set gzip to true if you are running phoenix.digest
|
||||||
|
# when deploying your static files in production.
|
||||||
|
plug Plug.Static,
|
||||||
|
at: "/", from: :proxichat, gzip: false,
|
||||||
|
only: ~w(css fonts images js favicon.ico robots.txt)
|
||||||
|
|
||||||
|
# Code reloading can be explicitly enabled under the
|
||||||
|
# :code_reloader configuration of your endpoint.
|
||||||
|
if code_reloading? do
|
||||||
|
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
|
||||||
|
plug Phoenix.LiveReloader
|
||||||
|
plug Phoenix.CodeReloader
|
||||||
|
end
|
||||||
|
|
||||||
|
plug Plug.RequestId
|
||||||
|
plug Plug.Logger
|
||||||
|
|
||||||
|
plug Plug.Parsers,
|
||||||
|
parsers: [:urlencoded, :multipart, :json],
|
||||||
|
pass: ["*/*"],
|
||||||
|
json_decoder: Poison
|
||||||
|
|
||||||
|
plug Plug.MethodOverride
|
||||||
|
plug Plug.Head
|
||||||
|
|
||||||
|
plug Plug.Session,
|
||||||
|
store: :cookie,
|
||||||
|
key: "_proxichat_key",
|
||||||
|
signing_salt: "GRoJxS7B"
|
||||||
|
|
||||||
|
plug Proxichat.Router
|
||||||
|
end
|
10
lib/proxichat/network_utils.ex
Normal file
10
lib/proxichat/network_utils.ex
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
defmodule Proxichat.NetworkUtils do
|
||||||
|
@accuracy 1
|
||||||
|
|
||||||
|
def get_network(lat, lon) do
|
||||||
|
[lat + 0.0, lon + 0.0]
|
||||||
|
|> Enum.map(fn coord -> Float.round coord, @accuracy end)
|
||||||
|
|> Enum.map(fn coord -> Float.to_string coord, decimals: @accuracy end)
|
||||||
|
|> Enum.join("x")
|
||||||
|
end
|
||||||
|
end
|
3
lib/proxichat/repo.ex
Normal file
3
lib/proxichat/repo.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule Proxichat.Repo do
|
||||||
|
use Ecto.Repo, otp_app: :proxichat
|
||||||
|
end
|
53
mix.exs
Normal file
53
mix.exs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
defmodule Proxichat.Mixfile do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[app: :proxichat,
|
||||||
|
version: "0.0.1",
|
||||||
|
elixir: "~> 1.0",
|
||||||
|
elixirc_paths: elixirc_paths(Mix.env),
|
||||||
|
compilers: [:phoenix] ++ Mix.compilers,
|
||||||
|
build_embedded: Mix.env == :prod,
|
||||||
|
start_permanent: Mix.env == :prod,
|
||||||
|
aliases: aliases,
|
||||||
|
deps: deps]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Configuration for the OTP application.
|
||||||
|
#
|
||||||
|
# Type `mix help compile.app` for more information.
|
||||||
|
def application do
|
||||||
|
[mod: {Proxichat, []},
|
||||||
|
applications: [:phoenix, :phoenix_html, :cowboy, :logger,
|
||||||
|
:phoenix_ecto, :postgrex]]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Specifies which paths to compile per environment.
|
||||||
|
defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
|
||||||
|
defp elixirc_paths(_), do: ["lib", "web"]
|
||||||
|
|
||||||
|
# Specifies your project dependencies.
|
||||||
|
#
|
||||||
|
# Type `mix help deps` for examples and options.
|
||||||
|
defp deps do
|
||||||
|
[{:phoenix, "~> 1.0.3"},
|
||||||
|
{:phoenix_ecto, "~> 1.1"},
|
||||||
|
{:postgrex, ">= 0.0.0"},
|
||||||
|
{:phoenix_html, "~> 2.1"},
|
||||||
|
{:phoenix_live_reload, "~> 1.0", only: :dev},
|
||||||
|
{:cowboy, "~> 1.0"},
|
||||||
|
{:uuid, "~> 1.1"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Aliases are shortcut or tasks specific to the current project.
|
||||||
|
# For example, to create, migrate and run the seeds file at once:
|
||||||
|
#
|
||||||
|
# $ mix ecto.setup
|
||||||
|
#
|
||||||
|
# See the documentation for `Mix` for more info on aliases.
|
||||||
|
defp aliases do
|
||||||
|
["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
|
||||||
|
"ecto.reset": ["ecto.drop", "ecto.setup"]]
|
||||||
|
end
|
||||||
|
end
|
15
mix.lock
Normal file
15
mix.lock
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
%{"cowboy": {:hex, :cowboy, "1.0.4"},
|
||||||
|
"cowlib": {:hex, :cowlib, "1.0.2"},
|
||||||
|
"decimal": {:hex, :decimal, "1.1.0"},
|
||||||
|
"ecto": {:hex, :ecto, "1.0.6"},
|
||||||
|
"fs": {:hex, :fs, "0.9.2"},
|
||||||
|
"phoenix": {:hex, :phoenix, "1.0.3"},
|
||||||
|
"phoenix_ecto": {:hex, :phoenix_ecto, "1.2.0"},
|
||||||
|
"phoenix_html": {:hex, :phoenix_html, "2.2.0"},
|
||||||
|
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.1"},
|
||||||
|
"plug": {:hex, :plug, "1.0.3"},
|
||||||
|
"poison": {:hex, :poison, "1.5.0"},
|
||||||
|
"poolboy": {:hex, :poolboy, "1.5.1"},
|
||||||
|
"postgrex": {:hex, :postgrex, "0.9.1"},
|
||||||
|
"ranch": {:hex, :ranch, "1.2.0"},
|
||||||
|
"uuid": {:hex, :uuid, "1.1.1"}}
|
12
package.json
Normal file
12
package.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"repository": {
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"brunch": "^1.8.5",
|
||||||
|
"babel-brunch": "^5.1.1",
|
||||||
|
"clean-css-brunch": ">= 1.0 < 1.8",
|
||||||
|
"css-brunch": ">= 1.0 < 1.8",
|
||||||
|
"javascript-brunch": ">= 1.0 < 1.8",
|
||||||
|
"uglify-js-brunch": ">= 1.0 < 1.8"
|
||||||
|
}
|
||||||
|
}
|
11
priv/repo/seeds.exs
Normal file
11
priv/repo/seeds.exs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# Script for populating the database. You can run it as:
|
||||||
|
#
|
||||||
|
# mix run priv/repo/seeds.exs
|
||||||
|
#
|
||||||
|
# Inside the script, you can read and write to any of your
|
||||||
|
# repositories directly:
|
||||||
|
#
|
||||||
|
# Proxichat.Repo.insert!(%SomeModel{})
|
||||||
|
#
|
||||||
|
# We recommend using the bang functions (`insert!`, `update!`
|
||||||
|
# and so on) as they will fail if something goes wrong.
|
8
test/controllers/page_controller_test.exs
Normal file
8
test/controllers/page_controller_test.exs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
defmodule Proxichat.PageControllerTest do
|
||||||
|
use Proxichat.ConnCase
|
||||||
|
|
||||||
|
test "GET /" do
|
||||||
|
conn = get conn(), "/"
|
||||||
|
assert html_response(conn, 200) =~ "Welcome to Phoenix!"
|
||||||
|
end
|
||||||
|
end
|
40
test/support/channel_case.ex
Normal file
40
test/support/channel_case.ex
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
defmodule Proxichat.ChannelCase do
|
||||||
|
@moduledoc """
|
||||||
|
This module defines the test case to be used by
|
||||||
|
channel tests.
|
||||||
|
|
||||||
|
Such tests rely on `Phoenix.ChannelTest` and also
|
||||||
|
imports other functionality to make it easier
|
||||||
|
to build and query models.
|
||||||
|
|
||||||
|
Finally, if the test case interacts with the database,
|
||||||
|
it cannot be async. For this reason, every test runs
|
||||||
|
inside a transaction which is reset at the beginning
|
||||||
|
of the test unless the test case is marked as async.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use ExUnit.CaseTemplate
|
||||||
|
|
||||||
|
using do
|
||||||
|
quote do
|
||||||
|
# Import conveniences for testing with channels
|
||||||
|
use Phoenix.ChannelTest
|
||||||
|
|
||||||
|
alias Proxichat.Repo
|
||||||
|
import Ecto.Model
|
||||||
|
import Ecto.Query, only: [from: 2]
|
||||||
|
|
||||||
|
|
||||||
|
# The default endpoint for testing
|
||||||
|
@endpoint Proxichat.Endpoint
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setup tags do
|
||||||
|
unless tags[:async] do
|
||||||
|
Ecto.Adapters.SQL.restart_test_transaction(Proxichat.Repo, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
41
test/support/conn_case.ex
Normal file
41
test/support/conn_case.ex
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
defmodule Proxichat.ConnCase do
|
||||||
|
@moduledoc """
|
||||||
|
This module defines the test case to be used by
|
||||||
|
tests that require setting up a connection.
|
||||||
|
|
||||||
|
Such tests rely on `Phoenix.ConnTest` and also
|
||||||
|
imports other functionality to make it easier
|
||||||
|
to build and query models.
|
||||||
|
|
||||||
|
Finally, if the test case interacts with the database,
|
||||||
|
it cannot be async. For this reason, every test runs
|
||||||
|
inside a transaction which is reset at the beginning
|
||||||
|
of the test unless the test case is marked as async.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use ExUnit.CaseTemplate
|
||||||
|
|
||||||
|
using do
|
||||||
|
quote do
|
||||||
|
# Import conveniences for testing with connections
|
||||||
|
use Phoenix.ConnTest
|
||||||
|
|
||||||
|
alias Proxichat.Repo
|
||||||
|
import Ecto.Model
|
||||||
|
import Ecto.Query, only: [from: 2]
|
||||||
|
|
||||||
|
import Proxichat.Router.Helpers
|
||||||
|
|
||||||
|
# The default endpoint for testing
|
||||||
|
@endpoint Proxichat.Endpoint
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setup tags do
|
||||||
|
unless tags[:async] do
|
||||||
|
Ecto.Adapters.SQL.restart_test_transaction(Proxichat.Repo, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
59
test/support/model_case.ex
Normal file
59
test/support/model_case.ex
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
defmodule Proxichat.ModelCase do
|
||||||
|
@moduledoc """
|
||||||
|
This module defines the test case to be used by
|
||||||
|
model tests.
|
||||||
|
|
||||||
|
You may define functions here to be used as helpers in
|
||||||
|
your model tests. See `errors_on/2`'s definition as reference.
|
||||||
|
|
||||||
|
Finally, if the test case interacts with the database,
|
||||||
|
it cannot be async. For this reason, every test runs
|
||||||
|
inside a transaction which is reset at the beginning
|
||||||
|
of the test unless the test case is marked as async.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use ExUnit.CaseTemplate
|
||||||
|
|
||||||
|
using do
|
||||||
|
quote do
|
||||||
|
alias Proxichat.Repo
|
||||||
|
import Ecto.Model
|
||||||
|
import Ecto.Query, only: [from: 2]
|
||||||
|
import Proxichat.ModelCase
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setup tags do
|
||||||
|
unless tags[:async] do
|
||||||
|
Ecto.Adapters.SQL.restart_test_transaction(Proxichat.Repo, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Helper for returning list of errors in model when passed certain data.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Given a User model that lists `:name` as a required field and validates
|
||||||
|
`:password` to be safe, it would return:
|
||||||
|
|
||||||
|
iex> errors_on(%User{}, %{password: "password"})
|
||||||
|
[password: "is unsafe", name: "is blank"]
|
||||||
|
|
||||||
|
You could then write your assertion like:
|
||||||
|
|
||||||
|
assert {:password, "is unsafe"} in errors_on(%User{}, %{password: "password"})
|
||||||
|
|
||||||
|
You can also create the changeset manually and retrieve the errors
|
||||||
|
field directly:
|
||||||
|
|
||||||
|
iex> changeset = User.changeset(%User{}, password: "password")
|
||||||
|
iex> {:password, "is unsafe"} in changeset.errors
|
||||||
|
true
|
||||||
|
"""
|
||||||
|
def errors_on(model, data) do
|
||||||
|
model.__struct__.changeset(model, data).errors
|
||||||
|
end
|
||||||
|
end
|
6
test/test_helper.exs
Normal file
6
test/test_helper.exs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
ExUnit.start
|
||||||
|
|
||||||
|
Mix.Task.run "ecto.create", ["--quiet"]
|
||||||
|
Mix.Task.run "ecto.migrate", ["--quiet"]
|
||||||
|
Ecto.Adapters.SQL.begin_test_transaction(Proxichat.Repo)
|
||||||
|
|
21
test/views/error_view_test.exs
Normal file
21
test/views/error_view_test.exs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule Proxichat.ErrorViewTest do
|
||||||
|
use Proxichat.ConnCase, async: true
|
||||||
|
|
||||||
|
# Bring render/3 and render_to_string/3 for testing custom views
|
||||||
|
import Phoenix.View
|
||||||
|
|
||||||
|
test "renders 404.html" do
|
||||||
|
assert render_to_string(Proxichat.ErrorView, "404.html", []) ==
|
||||||
|
"Page not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "render 500.html" do
|
||||||
|
assert render_to_string(Proxichat.ErrorView, "500.html", []) ==
|
||||||
|
"Server internal error"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "render any other" do
|
||||||
|
assert render_to_string(Proxichat.ErrorView, "505.html", []) ==
|
||||||
|
"Server internal error"
|
||||||
|
end
|
||||||
|
end
|
3
test/views/layout_view_test.exs
Normal file
3
test/views/layout_view_test.exs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule Proxichat.LayoutViewTest do
|
||||||
|
use Proxichat.ConnCase, async: true
|
||||||
|
end
|
3
test/views/page_view_test.exs
Normal file
3
test/views/page_view_test.exs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule Proxichat.PageViewTest do
|
||||||
|
use Proxichat.ConnCase, async: true
|
||||||
|
end
|
29
web/channels/meta_channel.ex
Normal file
29
web/channels/meta_channel.ex
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
defmodule Proxichat.MetaChannel do
|
||||||
|
use Phoenix.Channel
|
||||||
|
alias Phoenix.Token
|
||||||
|
|
||||||
|
alias Proxichat.Endpoint
|
||||||
|
alias Proxichat.ChannelAuthenticator
|
||||||
|
alias Proxichat.NetworkUtils
|
||||||
|
|
||||||
|
def join("meta:lobby", %{"lat" => lat, "lon" => lon}, socket) do
|
||||||
|
id = ChannelAuthenticator.create_user
|
||||||
|
network = NetworkUtils.get_network lat, lon
|
||||||
|
Endpoint.broadcast! "networks:#{network}", "here_i_am", %{user_id: id, lat: lat, lon: lon}
|
||||||
|
{:ok, %{user_id: id, goto: network}, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def join("meta:" <> user_id, message, socket) do
|
||||||
|
case Token.verify socket, "user", message.token do
|
||||||
|
{:ok, ^user_id} -> {:ok, %{msg: "yo"}, socket}
|
||||||
|
_ -> {:error, %{reason: "NOOOOOOOO"}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_in("new_loc", %{"body" => %{"lat" => lat, "lon" => lon}, "user_id" => id}, socket) do
|
||||||
|
new_network = NetworkUtils.get_network lat, lon
|
||||||
|
Endpoint.broadcast! "networks:#{new_network}", "here_i_am", %{user_id: id, lat: lat, lon: lon}
|
||||||
|
push socket, "goto", %{goto: new_network}
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
end
|
25
web/channels/network_channel.ex
Normal file
25
web/channels/network_channel.ex
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
defmodule Proxichat.NetworkChannel do
|
||||||
|
use Phoenix.Channel
|
||||||
|
|
||||||
|
alias Proxichat.ChannelAuthenticator
|
||||||
|
|
||||||
|
def join("networks:" <> network_id, %{"user_id" => id, "lat" => lat, "lon" => lon}, socket) do
|
||||||
|
send self, {:after_join, id, {lat, lon}}
|
||||||
|
{:ok, %{body: "Welcome to network #{network_id}, traveller! You shall henceforth be known as #{id}."}, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_in("new_msg", %{"body" => body, "user_id" => id}, socket) do
|
||||||
|
broadcast! socket, "new_msg", %{body: body, user_id: id}
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_in("kthxbye", %{"body" => _, "user_id" => id}, socket) do
|
||||||
|
broadcast! socket, "goodbye", %{user_id: id}
|
||||||
|
{:reply, :ok, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({:after_join, id, {lat, lon}}, socket) do
|
||||||
|
broadcast! socket, "fresh_blood", %{user_id: id, lat: lat, lon: lon}
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
end
|
38
web/channels/user_socket.ex
Normal file
38
web/channels/user_socket.ex
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
defmodule Proxichat.UserSocket do
|
||||||
|
use Phoenix.Socket
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
channel "meta:*", Proxichat.MetaChannel
|
||||||
|
channel "networks:*", Proxichat.NetworkChannel
|
||||||
|
|
||||||
|
## Transports
|
||||||
|
transport :websocket, Phoenix.Transports.WebSocket
|
||||||
|
# transport :longpoll, Phoenix.Transports.LongPoll
|
||||||
|
|
||||||
|
# Socket params are passed from the client and can
|
||||||
|
# be used to verify and authenticate a user. After
|
||||||
|
# verification, you can put default assigns into
|
||||||
|
# the socket that will be set for all channels, ie
|
||||||
|
#
|
||||||
|
# {:ok, assign(socket, :user_id, verified_user_id)}
|
||||||
|
#
|
||||||
|
# To deny connection, return `:error`.
|
||||||
|
#
|
||||||
|
# See `Phoenix.Token` documentation for examples in
|
||||||
|
# performing token verification on connect.
|
||||||
|
def connect(_params, socket) do
|
||||||
|
{:ok, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Socket id's are topics that allow you to identify all sockets for a given user:
|
||||||
|
#
|
||||||
|
# def id(socket), do: "users_socket:#{socket.assigns.user_id}"
|
||||||
|
#
|
||||||
|
# Would allow you to broadcast a "disconnect" event and terminate
|
||||||
|
# all active sockets and channels for a given user:
|
||||||
|
#
|
||||||
|
# Proxichat.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{})
|
||||||
|
#
|
||||||
|
# Returning `nil` makes this socket anonymous.
|
||||||
|
def id(_socket), do: nil
|
||||||
|
end
|
7
web/controllers/page_controller.ex
Normal file
7
web/controllers/page_controller.ex
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
defmodule Proxichat.PageController do
|
||||||
|
use Proxichat.Web, :controller
|
||||||
|
|
||||||
|
def index(conn, _params) do
|
||||||
|
render conn, "index.html"
|
||||||
|
end
|
||||||
|
end
|
26
web/router.ex
Normal file
26
web/router.ex
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
defmodule Proxichat.Router do
|
||||||
|
use Proxichat.Web, :router
|
||||||
|
|
||||||
|
pipeline :browser do
|
||||||
|
plug :accepts, ["html"]
|
||||||
|
plug :fetch_session
|
||||||
|
plug :fetch_flash
|
||||||
|
plug :protect_from_forgery
|
||||||
|
plug :put_secure_browser_headers
|
||||||
|
end
|
||||||
|
|
||||||
|
pipeline :api do
|
||||||
|
plug :accepts, ["json"]
|
||||||
|
end
|
||||||
|
|
||||||
|
scope "/", Proxichat do
|
||||||
|
pipe_through :browser # Use the default browser stack
|
||||||
|
|
||||||
|
get "/", PageController, :index
|
||||||
|
end
|
||||||
|
|
||||||
|
# Other scopes may use custom stacks.
|
||||||
|
# scope "/api", Proxichat do
|
||||||
|
# pipe_through :api
|
||||||
|
# end
|
||||||
|
end
|
BIN
web/static/assets/favicon.ico
Normal file
BIN
web/static/assets/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
web/static/assets/images/pissed_off.png
Normal file
BIN
web/static/assets/images/pissed_off.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4 KiB |
5
web/static/assets/robots.txt
Normal file
5
web/static/assets/robots.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
|
||||||
|
#
|
||||||
|
# To ban all spiders from the entire site uncomment the next two lines:
|
||||||
|
# User-agent: *
|
||||||
|
# Disallow: /
|
89
web/static/css/app.css
Normal file
89
web/static/css/app.css
Normal file
File diff suppressed because one or more lines are too long
21
web/static/js/app.js
Normal file
21
web/static/js/app.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// Brunch automatically concatenates all files in your
|
||||||
|
// watched paths. Those paths can be configured at
|
||||||
|
// config.paths.watched in "brunch-config.js".
|
||||||
|
//
|
||||||
|
// However, those files will only be executed if
|
||||||
|
// explicitly imported. The only exception are files
|
||||||
|
// in vendor, which are never wrapped in imports and
|
||||||
|
// therefore are always executed.
|
||||||
|
|
||||||
|
// Import dependencies
|
||||||
|
//
|
||||||
|
// If you no longer want to use a dependency, remember
|
||||||
|
// to also remove its path from "config.paths.watched".
|
||||||
|
import "deps/phoenix_html/web/static/js/phoenix_html"
|
||||||
|
|
||||||
|
// Import local files
|
||||||
|
//
|
||||||
|
// Local files can be imported directly using relative
|
||||||
|
// paths "./socket" or full ones "web/static/js/socket".
|
||||||
|
|
||||||
|
import socket from "./socket"
|
256
web/static/js/socket.js
Normal file
256
web/static/js/socket.js
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
// NOTE: The contents of this file will only be executed if
|
||||||
|
// you uncomment its entry in "web/static/js/app.js".
|
||||||
|
|
||||||
|
// To use Phoenix channels, the first step is to import Socket
|
||||||
|
// and connect at the socket path in "lib/my_app/endpoint.ex":
|
||||||
|
import {Socket} from "deps/phoenix/web/static/js/phoenix"
|
||||||
|
|
||||||
|
let socket = new Socket("/socket", {params: {token: window.userToken}})
|
||||||
|
|
||||||
|
// When you connect, you'll often need to authenticate the client.
|
||||||
|
// For example, imagine you have an authentication plug, `MyAuth`,
|
||||||
|
// which authenticates the session and assigns a `:current_user`.
|
||||||
|
// If the current user exists you can assign the user's token in
|
||||||
|
// the connection for use in the layout.
|
||||||
|
//
|
||||||
|
// In your "web/router.ex":
|
||||||
|
//
|
||||||
|
// pipeline :browser do
|
||||||
|
// ...
|
||||||
|
// plug MyAuth
|
||||||
|
// plug :put_user_token
|
||||||
|
// end
|
||||||
|
//
|
||||||
|
// defp put_user_token(conn, _) do
|
||||||
|
// if current_user = conn.assigns[:current_user] do
|
||||||
|
// token = Phoenix.Token.sign(conn, "user socket", current_user.id)
|
||||||
|
// assign(conn, :user_token, token)
|
||||||
|
// else
|
||||||
|
// conn
|
||||||
|
// end
|
||||||
|
// end
|
||||||
|
//
|
||||||
|
// Now you need to pass this token to JavaScript. You can do so
|
||||||
|
// inside a script tag in "web/templates/layout/app.html.eex":
|
||||||
|
//
|
||||||
|
// <script>window.userToken = "<%= assigns[:user_token] %>";</script>
|
||||||
|
//
|
||||||
|
// You will need to verify the user token in the "connect/2" function
|
||||||
|
// in "web/channels/user_socket.ex":
|
||||||
|
//
|
||||||
|
// def connect(%{"token" => token}, socket) do
|
||||||
|
// # max_age: 1209600 is equivalent to two weeks in seconds
|
||||||
|
// case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
|
||||||
|
// {:ok, user_id} ->
|
||||||
|
// {:ok, assign(socket, :user, user_id)}
|
||||||
|
// {:error, reason} ->
|
||||||
|
// :error
|
||||||
|
// end
|
||||||
|
// end
|
||||||
|
//
|
||||||
|
// Finally, pass the token on connect as below. Or remove it
|
||||||
|
// from connect if you don't care about authentication.
|
||||||
|
|
||||||
|
socket.connect();
|
||||||
|
|
||||||
|
let start_loc = [61.2, 24.6];
|
||||||
|
let current_loc = start_loc;
|
||||||
|
|
||||||
|
let user_id = null;
|
||||||
|
let network = null;
|
||||||
|
let network_channel = null;
|
||||||
|
let text_input = document.getElementById('chat_text_field');
|
||||||
|
let input_form = document.getElementById('chat_form');
|
||||||
|
let chat_log = document.getElementById('chat_log');
|
||||||
|
let other_locs = {};
|
||||||
|
|
||||||
|
input_form.addEventListener('submit', e => {
|
||||||
|
network_out(text_input.value);
|
||||||
|
text_input.value = '';
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now that you are connected, you can join channels with a topic:
|
||||||
|
let lobby_channel = socket.channel('meta:lobby', {
|
||||||
|
lat: start_loc[0],
|
||||||
|
lon: start_loc[1]
|
||||||
|
});
|
||||||
|
|
||||||
|
lobby_channel.join()
|
||||||
|
.receive('ok', resp => {
|
||||||
|
console.log('Joined successfully', resp);
|
||||||
|
|
||||||
|
user_id = resp.user_id;
|
||||||
|
window.userToken = resp.token;
|
||||||
|
join_network(resp.goto);
|
||||||
|
})
|
||||||
|
.receive('error', resp => {
|
||||||
|
console.log('Unable to join', resp);
|
||||||
|
});
|
||||||
|
|
||||||
|
lobby_channel.on('goto', msg => {
|
||||||
|
change_network(msg.goto);
|
||||||
|
});
|
||||||
|
|
||||||
|
function connect_network() {
|
||||||
|
console.log('Connecting to ' + network + '…');
|
||||||
|
|
||||||
|
network_channel = socket.channel('networks:' + network, {
|
||||||
|
user_id: user_id,
|
||||||
|
lat: current_loc[0],
|
||||||
|
lon: current_loc[1]
|
||||||
|
});
|
||||||
|
|
||||||
|
network_channel.join()
|
||||||
|
.receive('ok', resp => {
|
||||||
|
print_log('Joined network ' + network + '.');
|
||||||
|
text_input.disabled = false;
|
||||||
|
network_in(resp);
|
||||||
|
})
|
||||||
|
.receive('error', resp => {
|
||||||
|
throw new Error('Could not join network!');
|
||||||
|
});
|
||||||
|
|
||||||
|
network_channel.on('new_msg', network_in);
|
||||||
|
network_channel.on('here_i_am', network_loc_in);
|
||||||
|
network_channel.on('fresh_blood', network_join);
|
||||||
|
network_channel.on('goodbye', network_part);
|
||||||
|
}
|
||||||
|
|
||||||
|
function network_in(msg) {
|
||||||
|
let prefix = null;
|
||||||
|
if (msg.user_id) {
|
||||||
|
prefix = '<' + msg.user_id + '> ';
|
||||||
|
}
|
||||||
|
|
||||||
|
print_log(msg.body, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
function network_loc_in(data) {
|
||||||
|
// I don't hate you
|
||||||
|
if (data.user_id == user_id) return;
|
||||||
|
|
||||||
|
handle_loc_in(data.user_id, data.lat, data.lon);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_loc_in(id, lat, lon) {
|
||||||
|
clear_user_loc(id)
|
||||||
|
|
||||||
|
let marker = new google.maps.Marker({
|
||||||
|
map: map,
|
||||||
|
position: new google.maps.LatLng(lat, lon),
|
||||||
|
draggable: false,
|
||||||
|
icon: {url: '/images/pissed_off.png'}
|
||||||
|
});
|
||||||
|
|
||||||
|
other_locs[id] = {
|
||||||
|
marker: marker
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear_user_loc(id) {
|
||||||
|
if (id in other_locs) {
|
||||||
|
other_locs[id].marker.setMap(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear_all_user_loc() {
|
||||||
|
for (var id in other_locs) {
|
||||||
|
if (other_locs.hasOwnProperty(id)) {
|
||||||
|
clear_user_loc(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function network_join(data) {
|
||||||
|
if (data.user_id === user_id) return;
|
||||||
|
|
||||||
|
handle_loc_in(data.user_id, data.lat, data.lon);
|
||||||
|
print_log(data.user_id + ' has joined the network!');
|
||||||
|
}
|
||||||
|
|
||||||
|
function network_part(data) {
|
||||||
|
if (data.user_id === user_id) {
|
||||||
|
clear_all_user_loc();
|
||||||
|
print_log('You left the network ' + network + '. You\'re better off without those suckers anyway.');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clear_user_loc(data.user_id);
|
||||||
|
print_log(data.user_id + ' has left… 😭');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function network_out(msg) {
|
||||||
|
if (msg === '') return;
|
||||||
|
|
||||||
|
send_to('new_msg', network_channel, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_loc(lat, lon) {
|
||||||
|
send_to('new_loc', lobby_channel, {
|
||||||
|
lat: lat,
|
||||||
|
lon: lon
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function send_to(type, channel, data) {
|
||||||
|
return channel.push(type, {body: data, user_id: user_id});
|
||||||
|
}
|
||||||
|
|
||||||
|
function print_log(msg, prefix) {
|
||||||
|
prefix = prefix || '*** ';
|
||||||
|
|
||||||
|
let keep_pos = false;
|
||||||
|
if (chat_log.scrollHeight <= (chat_log.scrollTop + chat_log.offsetHeight)) {
|
||||||
|
keep_pos = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
chat_log.value += '\n' + prefix + msg;
|
||||||
|
|
||||||
|
if (keep_pos) {
|
||||||
|
chat_log.scrollTop = chat_log.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function change_network(new_network) {
|
||||||
|
if (new_network === network) return;
|
||||||
|
|
||||||
|
text_input.disabled = true;
|
||||||
|
|
||||||
|
send_to('kthxbye', network_channel, '').receive('ok', resp => {
|
||||||
|
network_channel.leave().receive('ok', resp => {
|
||||||
|
join_network(new_network);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function join_network(new_network) {
|
||||||
|
network = new_network;
|
||||||
|
connect_network();
|
||||||
|
}
|
||||||
|
|
||||||
|
let map = null;
|
||||||
|
let marker = null;
|
||||||
|
|
||||||
|
function init_map() {
|
||||||
|
var myOptions = {
|
||||||
|
zoom: 12,
|
||||||
|
center: new google.maps.LatLng(start_loc[0], start_loc[1]),
|
||||||
|
mapTypeId: google.maps.MapTypeId.ROADMAP
|
||||||
|
};
|
||||||
|
map = new google.maps.Map(document.getElementById('gmap_canvas'), myOptions);
|
||||||
|
marker = new google.maps.Marker({
|
||||||
|
map: map,
|
||||||
|
position: new google.maps.LatLng(start_loc[0], start_loc[1]),
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
google.maps.event.addListener(marker, 'dragend', evt => {
|
||||||
|
current_loc = [evt.latLng.lat(), evt.latLng.lng()];
|
||||||
|
update_loc(current_loc[0], current_loc[1]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
google.maps.event.addDomListener(window, 'load', init_map);
|
||||||
|
|
||||||
|
export default socket
|
22
web/templates/layout/app.html.eex
Normal file
22
web/templates/layout/app.html.eex
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="">
|
||||||
|
|
||||||
|
<title>Proxichat</title>
|
||||||
|
<link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container" role="main">
|
||||||
|
|
||||||
|
<%= @inner %>
|
||||||
|
|
||||||
|
</div> <!-- /container -->
|
||||||
|
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
16
web/templates/page/index.html.eex
Normal file
16
web/templates/page/index.html.eex
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<script type="text/javascript" src="//maps.google.com/maps/api/js?sensor=false"></script>
|
||||||
|
<div style="overflow:hidden;height:300px;width:100%;">
|
||||||
|
<div id="gmap_canvas" style="height:300px;width:100%;"></div>
|
||||||
|
<style>
|
||||||
|
#gmap_canvas img {
|
||||||
|
max-width: none !important;
|
||||||
|
background: none !important
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<a class="google-map-code" href="http://www.themecircle.net" id="get-map-data"></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="chat_form" action="" method="post">
|
||||||
|
<textarea id="chat_log" placeholder="Start chatting!" readonly></textarea>
|
||||||
|
<input id="chat_text_field" type="text" placeholder="Type here to chat, press enter to send" autocomplete="off" disabled="disabled" />
|
||||||
|
</form>
|
17
web/views/error_view.ex
Normal file
17
web/views/error_view.ex
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule Proxichat.ErrorView do
|
||||||
|
use Proxichat.Web, :view
|
||||||
|
|
||||||
|
def render("404.html", _assigns) do
|
||||||
|
"Page not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("500.html", _assigns) do
|
||||||
|
"Server internal error"
|
||||||
|
end
|
||||||
|
|
||||||
|
# In case no render clause matches or no
|
||||||
|
# template is found, let's render it as 500
|
||||||
|
def template_not_found(_template, assigns) do
|
||||||
|
render "500.html", assigns
|
||||||
|
end
|
||||||
|
end
|
3
web/views/layout_view.ex
Normal file
3
web/views/layout_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule Proxichat.LayoutView do
|
||||||
|
use Proxichat.Web, :view
|
||||||
|
end
|
3
web/views/page_view.ex
Normal file
3
web/views/page_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule Proxichat.PageView do
|
||||||
|
use Proxichat.Web, :view
|
||||||
|
end
|
76
web/web.ex
Normal file
76
web/web.ex
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
defmodule Proxichat.Web do
|
||||||
|
@moduledoc """
|
||||||
|
A module that keeps using definitions for controllers,
|
||||||
|
views and so on.
|
||||||
|
|
||||||
|
This can be used in your application as:
|
||||||
|
|
||||||
|
use Proxichat.Web, :controller
|
||||||
|
use Proxichat.Web, :view
|
||||||
|
|
||||||
|
The definitions below will be executed for every view,
|
||||||
|
controller, etc, so keep them short and clean, focused
|
||||||
|
on imports, uses and aliases.
|
||||||
|
|
||||||
|
Do NOT define functions inside the quoted expressions
|
||||||
|
below.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def model do
|
||||||
|
quote do
|
||||||
|
use Ecto.Model
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query, only: [from: 1, from: 2]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def controller do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Controller
|
||||||
|
|
||||||
|
alias Proxichat.Repo
|
||||||
|
import Ecto.Model
|
||||||
|
import Ecto.Query, only: [from: 1, from: 2]
|
||||||
|
|
||||||
|
import Proxichat.Router.Helpers
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def view do
|
||||||
|
quote do
|
||||||
|
use Phoenix.View, root: "web/templates"
|
||||||
|
|
||||||
|
# Import convenience functions from controllers
|
||||||
|
import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
|
||||||
|
|
||||||
|
# Use all HTML functionality (forms, tags, etc)
|
||||||
|
use Phoenix.HTML
|
||||||
|
|
||||||
|
import Proxichat.Router.Helpers
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def router do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Router
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def channel do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Channel
|
||||||
|
|
||||||
|
alias Proxichat.Repo
|
||||||
|
import Ecto.Model
|
||||||
|
import Ecto.Query, only: [from: 1, from: 2]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
When used, dispatch to the appropriate controller/view/etc.
|
||||||
|
"""
|
||||||
|
defmacro __using__(which) when is_atom(which) do
|
||||||
|
apply(__MODULE__, which, [])
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue