diff options
author | href <href@random.sh> | 2018-01-31 20:36:42 +0100 |
---|---|---|
committer | href <href@random.sh> | 2018-01-31 20:36:42 +0100 |
commit | fcb2a082346054108f3f16cffe968450cf961976 (patch) | |
tree | 8e7d2eb9eeebf854e62e08c29b147f34dfeb1881 /lib |
import
Diffstat (limited to 'lib')
-rw-r--r-- | lib/lsg.ex | 9 | ||||
-rw-r--r-- | lib/lsg/application.ex | 37 | ||||
-rw-r--r-- | lib/lsg/icecast.ex | 117 | ||||
-rw-r--r-- | lib/lsg/icecast_agent.ex | 17 | ||||
-rw-r--r-- | lib/lsg_irc.ex | 2 | ||||
-rw-r--r-- | lib/lsg_irc/broadcast_handler.ex | 43 | ||||
-rw-r--r-- | lib/lsg_irc/connection_handler.ex | 36 | ||||
-rw-r--r-- | lib/lsg_irc/login_handler.ex | 25 | ||||
-rw-r--r-- | lib/lsg_irc/np_handler.ex | 32 | ||||
-rw-r--r-- | lib/lsg_web.ex | 67 | ||||
-rw-r--r-- | lib/lsg_web/channels/user_socket.ex | 37 | ||||
-rw-r--r-- | lib/lsg_web/controllers/page_controller.ex | 20 | ||||
-rw-r--r-- | lib/lsg_web/endpoint.ex | 57 | ||||
-rw-r--r-- | lib/lsg_web/gettext.ex | 24 | ||||
-rw-r--r-- | lib/lsg_web/router.ex | 26 | ||||
-rw-r--r-- | lib/lsg_web/templates/layout/app.html.eex | 35 | ||||
-rw-r--r-- | lib/lsg_web/templates/page/index.html.eex | 36 | ||||
-rw-r--r-- | lib/lsg_web/templates/page/widget.html.eex | 11 | ||||
-rw-r--r-- | lib/lsg_web/views/error_helpers.ex | 40 | ||||
-rw-r--r-- | lib/lsg_web/views/error_view.ex | 17 | ||||
-rw-r--r-- | lib/lsg_web/views/layout_view.ex | 3 | ||||
-rw-r--r-- | lib/lsg_web/views/page_view.ex | 3 |
22 files changed, 694 insertions, 0 deletions
diff --git a/lib/lsg.ex b/lib/lsg.ex new file mode 100644 index 0000000..d2076f8 --- /dev/null +++ b/lib/lsg.ex @@ -0,0 +1,9 @@ +defmodule LSG do + @moduledoc """ + LSG keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ +end diff --git a/lib/lsg/application.ex b/lib/lsg/application.ex new file mode 100644 index 0000000..73ec04d --- /dev/null +++ b/lib/lsg/application.ex @@ -0,0 +1,37 @@ +defmodule LSG.Application do + use Application + + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + def start(_type, _args) do + import Supervisor.Spec + {:ok, irc_client} = ExIRC.start_link! + + # Define workers and child supervisors to be supervised + children = [ + # Start the endpoint when the application starts + supervisor(LSGWeb.Endpoint, []), + # Start your own worker by calling: LSG.Worker.start_link(arg1, arg2, arg3) + # worker(LSG.Worker, [arg1, arg2, arg3]), + worker(Registry, [[keys: :duplicate, name: LSG.BroadcastRegistry]]), + worker(LSG.IcecastAgent, []), + worker(LSG.Icecast, []), + worker(LSG.IRC.ConnectionHandler, [irc_client]), + worker(LSG.IRC.LoginHandler, [irc_client]), + worker(LSG.IRC.BroadcastHandler, [irc_client]), + worker(LSG.IRC.NpHandler, [irc_client]), + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: LSG.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 + LSGWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/lib/lsg/icecast.ex b/lib/lsg/icecast.ex new file mode 100644 index 0000000..ec168c3 --- /dev/null +++ b/lib/lsg/icecast.ex @@ -0,0 +1,117 @@ +defmodule LSG.Icecast do + use GenServer + require Logger + @interval 10_000 + @hackney_pool :default + @httpoison_opts [hackney: [pool: @hackney_pool]] + @fuse __MODULE__ + + def start_link, do: GenServer.start_link(__MODULE__, [], []) + + def init(_) do + GenServer.cast(self(), :poll) + {:ok, nil} + end + + def handle_cast(:poll, state) do + state = poll(state) + {:noreply, state} + end + + def handle_info(:poll, state) do + state = poll(state) + {:noreply, state} + end + + defp poll(state) do + state = case request(base_url(), :get) do + {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + #update_json_stats(Jason.decode(body)) + stats = update_stats(body) + if state != stats do + Logger.info "Icecast Update: " <> inspect(stats) + LSG.IcecastAgent.update(stats) + Registry.dispatch(LSG.BroadcastRegistry, "icecast", fn ws -> + for {pid, _} <- ws, do: send(pid, {:icecast, stats}) + end) + stats + else + state + end + error -> + Logger.error "Icecast HTTP Error: #{inspect error}" + state + end + :timer.send_after(5_000, :poll) + state + end + + defp update_stats(html) do + raw = Floki.find(html, "div.roundbox") + |> Enum.map(fn(html) -> + html = Floki.raw_html(html) + [{"h3", _, ["Mount Point /"<>mount]}] = Floki.find(html, "h3.mount") + stats = Floki.find(html, "tr") + |> Enum.map(fn({"tr", _, tds}) -> + [{"td", _, keys}, {"td", _, values}] = tds + key = List.first(keys) + value = List.first(values) + {key, value} + end) + |> Enum.into(Map.new) + {mount, stats} + end) + |> Enum.into(Map.new) + + live? = if Map.get(raw["live"], "Content Type:", false), do: true, else: false + np = if live? do + raw["live"]["Currently playing:"] + else + raw["autodj"]["Currently playing:"] + end + + genre = raw["live"]["Genre:"] || nil + %{np: np || "", live: live? || false, genre: genre} + end + + defp update_json_stats({:ok, body}) do + Logger.debug "JSON STATS: #{inspect body}" + end + + defp update_json_stats(error) do + Logger.error "Failed to decode JSON Stats: #{inspect error}" + end + + defp request(uri, method, body \\ [], headers \\ []) do + headers = [{"user-agent", "LSG-API[lsg.goulag.org] href@random.sh"}] ++ headers + options = @httpoison_opts + case :ok do #:fuse.ask(@fuse, :sync) do + :ok -> run_request(method, uri, body, headers, options) + :blown -> :blown + end + end + + # This is to work around hackney's behaviour of returning `{:error, :closed}` when a pool connection has been closed + # (keep-alive expired). We just retry the request immediatly up to five times. + defp run_request(method, uri, body, headers, options), do: run_request(method, uri, body, headers, options, 0) + defp run_request(method, uri, body, headers, options, retries) when retries < 4 do + case HTTPoison.request(method, uri, body, headers, options) do + {:error, :closed} -> run_request(method, uri, body, headers, options, retries + 1) + other -> other + end + end + defp run_request(method, uri, body, headers, options, _exceeded_retries), do: {:error, :unavailable} + + # + # -- URIs + # + + defp stats_json_url do + base_url() <> "/status-json.xsl" + end + + defp base_url do + "http://91.121.59.45:8089" + end + +end diff --git a/lib/lsg/icecast_agent.ex b/lib/lsg/icecast_agent.ex new file mode 100644 index 0000000..8f8a86a --- /dev/null +++ b/lib/lsg/icecast_agent.ex @@ -0,0 +1,17 @@ +defmodule LSG.IcecastAgent do + use Agent + + def start_link() do + Agent.start_link(fn -> nil end, name: __MODULE__) + end + + def update(stats) do + Agent.update(__MODULE__, fn(_old) -> stats end) + end + + def get do + Agent.get(__MODULE__, fn(stats) -> stats end) + end + +end + diff --git a/lib/lsg_irc.ex b/lib/lsg_irc.ex new file mode 100644 index 0000000..751c9c9 --- /dev/null +++ b/lib/lsg_irc.ex @@ -0,0 +1,2 @@ +defmodule LSG.IRC do +end diff --git a/lib/lsg_irc/broadcast_handler.ex b/lib/lsg_irc/broadcast_handler.ex new file mode 100644 index 0000000..22ffbaf --- /dev/null +++ b/lib/lsg_irc/broadcast_handler.ex @@ -0,0 +1,43 @@ +defmodule LSG.IRC.BroadcastHandler do + def start_link(client) do + GenServer.start_link(__MODULE__, [client]) + end + + def init([client]) do + ExIRC.Client.add_handler client, self + {:ok, _} = Registry.register(LSG.BroadcastRegistry, "icecast", []) + {:ok, {client, ["#lsg"], false}} + end + + def handle_info({:icecast, stats = %{live: true}}, s = {client, channels, true}) do + for c <- channels do + ExIRC.Client.msg(client, :privmsg, c, "en direct: "<>format_genre(stats.genre) <> stats.np) + end + {:noreply, s} + end + + def handle_info({:icecast, stats = %{live: true}}, s = {client, channels, false}) do + for c <- channels do + ExIRC.Client.msg(client, :privmsg, c, "115ANS EN DIREK! " <> format_genre(stats.genre) <> stats.np) + end + {:noreply, {client, channels, true}} + end + + def handle_info({:icecast, stats = %{live: false}}, s = {client, channels, true}) do + for c <- channels do + ExIRC.Client.msg(client, :privmsg, c, "Le direct c'est fini, retour aux Smashing Pumpkins...") + ExIRC.Client.msg(client, :privmsg, c, "np: " <> stats.np) + end + {:noreply, {client, channels, false}} + end + + # Catch-all for messages you don't care about + def handle_info(msg, state) do + {:noreply, state} + end + + defp format_genre(nil), do: "" + defp format_genre(""), do: "" + defp format_genre(text), do: "[#{text}] " + +end diff --git a/lib/lsg_irc/connection_handler.ex b/lib/lsg_irc/connection_handler.ex new file mode 100644 index 0000000..f3bb1d4 --- /dev/null +++ b/lib/lsg_irc/connection_handler.ex @@ -0,0 +1,36 @@ +defmodule LSG.IRC.ConnectionHandler do + defmodule State do + defstruct host: "irc.quakenet.org", + port: 6667, + pass: "", + nick: "bot115ans", + user: "115ans", + name: "115ans.net", + client: nil + end + + def start_link(client) do + GenServer.start_link(__MODULE__, [%State{client: client}]) + end + + def init([state]) do + ExIRC.Client.add_handler state.client, self + ExIRC.Client.connect! state.client, state.host, state.port + {:ok, state} + end + + def handle_info({:connected, server, port}, state) do + debug "Connected to #{server}:#{port}" + ExIRC.Client.logon state.client, state.pass, state.nick, state.user, state.name + {:noreply, state} + end + + # Catch-all for messages you don't care about + def handle_info(msg, state) do + {:noreply, state} + end + + defp debug(msg) do + IO.puts IO.ANSI.yellow() <> msg <> IO.ANSI.reset() + end +end diff --git a/lib/lsg_irc/login_handler.ex b/lib/lsg_irc/login_handler.ex new file mode 100644 index 0000000..b4757b1 --- /dev/null +++ b/lib/lsg_irc/login_handler.ex @@ -0,0 +1,25 @@ +defmodule LSG.IRC.LoginHandler do + def start_link(client) do + GenServer.start_link(__MODULE__, [client]) + end + + def init([client]) do + ExIRC.Client.add_handler client, self + {:ok, {client, ["#lsg"]}} + end + + def handle_info(:logged_in, state = {client, channels}) do + debug "Logged in to server" + channels |> Enum.map(&ExIRC.Client.join client, &1) + {:noreply, state} + end + + # Catch-all for messages you don't care about + def handle_info(_msg, state) do + {:noreply, state} + end + + defp debug(msg) do + IO.puts IO.ANSI.yellow() <> msg <> IO.ANSI.reset() + end +end diff --git a/lib/lsg_irc/np_handler.ex b/lib/lsg_irc/np_handler.ex new file mode 100644 index 0000000..8bde293 --- /dev/null +++ b/lib/lsg_irc/np_handler.ex @@ -0,0 +1,32 @@ +defmodule LSG.IRC.NpHandler do + def start_link(client) do + GenServer.start_link(__MODULE__, [client]) + end + + def init([client]) do + ExIRC.Client.add_handler client, self + {:ok, _} = Registry.register(LSG.BroadcastRegistry, "icecast", []) + {:ok, client} + end + + def handle_info({:received, "!np", _, chan}, client) do + stats = LSG.IcecastAgent.get + np = if stats.live do + "en direct: #{format_genre(stats.genre)}#{stats.np}" + else + "np: #{stats.np}" + end + ExIRC.Client.msg(client, :privmsg, chan, np) + {:noreply, client} + end + + # Catch-all for messages you don't care about + def handle_info(msg, client) do + {:noreply, client} + end + + defp format_genre(nil), do: "" + defp format_genre(""), do: "" + defp format_genre(text), do: "[#{text}] " + +end diff --git a/lib/lsg_web.ex b/lib/lsg_web.ex new file mode 100644 index 0000000..e07d648 --- /dev/null +++ b/lib/lsg_web.ex @@ -0,0 +1,67 @@ +defmodule LSGWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, views, channels and so on. + + This can be used in your application as: + + use LSGWeb, :controller + use LSGWeb, :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. Instead, define any helper function in modules + and import those modules here. + """ + + def controller do + quote do + use Phoenix.Controller, namespace: LSGWeb + import Plug.Conn + import LSGWeb.Router.Helpers + import LSGWeb.Gettext + end + end + + def view do + quote do + use Phoenix.View, root: "lib/lsg_web/templates", + namespace: LSGWeb + + # Import convenience functions from controllers + import Phoenix.Controller, only: [get_flash: 2, view_module: 1] + + # Use all HTML functionality (forms, tags, etc) + use Phoenix.HTML + + import LSGWeb.Router.Helpers + import LSGWeb.ErrorHelpers + import LSGWeb.Gettext + end + end + + def router do + quote do + use Phoenix.Router + import Plug.Conn + import Phoenix.Controller + end + end + + def channel do + quote do + use Phoenix.Channel + import LSGWeb.Gettext + 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 diff --git a/lib/lsg_web/channels/user_socket.ex b/lib/lsg_web/channels/user_socket.ex new file mode 100644 index 0000000..6fedf18 --- /dev/null +++ b/lib/lsg_web/channels/user_socket.ex @@ -0,0 +1,37 @@ +defmodule LSGWeb.UserSocket do + use Phoenix.Socket + + ## Channels + # channel "room:*", LSGWeb.RoomChannel + + ## 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: "user_socket:#{socket.assigns.user_id}" + # + # Would allow you to broadcast a "disconnect" event and terminate + # all active sockets and channels for a given user: + # + # LSGWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) + # + # Returning `nil` makes this socket anonymous. + def id(_socket), do: nil +end diff --git a/lib/lsg_web/controllers/page_controller.ex b/lib/lsg_web/controllers/page_controller.ex new file mode 100644 index 0000000..3d4e444 --- /dev/null +++ b/lib/lsg_web/controllers/page_controller.ex @@ -0,0 +1,20 @@ +defmodule LSGWeb.PageController do + use LSGWeb, :controller + + def index(conn, _params) do + render conn, "index.html" + end + + def icecast(conn, _params) do + conn + |> json(LSG.IcecastAgent.get) + end + + def widget(conn, options) do + icecast = LSG.IcecastAgent.get + conn + |> put_layout(false) + |> render("widget.html", icecast: icecast) + end + +end diff --git a/lib/lsg_web/endpoint.ex b/lib/lsg_web/endpoint.ex new file mode 100644 index 0000000..e05e3f5 --- /dev/null +++ b/lib/lsg_web/endpoint.ex @@ -0,0 +1,57 @@ +defmodule LSGWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :lsg + + socket "/socket", LSGWeb.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: "/assets", from: :lsg, 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 + + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + plug Plug.Session, + store: :cookie, + key: "_lsg_key", + signing_salt: "+p7K3wrj" + + plug LSGWeb.Router + + @doc """ + Callback invoked for dynamically configuring the endpoint. + + It receives the endpoint configuration and checks if + configuration should be loaded from the system environment. + """ + def init(_key, config) do + if config[:load_from_system_env] do + port = System.get_env("PORT") || raise "expected the PORT environment variable to be set" + {:ok, Keyword.put(config, :http, [:inet6, port: port])} + else + {:ok, config} + end + end +end diff --git a/lib/lsg_web/gettext.ex b/lib/lsg_web/gettext.ex new file mode 100644 index 0000000..f38a57d --- /dev/null +++ b/lib/lsg_web/gettext.ex @@ -0,0 +1,24 @@ +defmodule LSGWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), + your module gains a set of macros for translations, for example: + + import LSGWeb.Gettext + + # Simple translation + gettext "Here is the string to translate" + + # Plural translation + ngettext "Here is the string to translate", + "Here are the strings to translate", + 3 + + # Domain-based translation + dgettext "errors", "Here is the error message to translate" + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext, otp_app: :lsg +end diff --git a/lib/lsg_web/router.ex b/lib/lsg_web/router.ex new file mode 100644 index 0000000..773a4c2 --- /dev/null +++ b/lib/lsg_web/router.ex @@ -0,0 +1,26 @@ +defmodule LSGWeb.Router do + use LSGWeb, :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 "/", LSGWeb do + pipe_through :browser # Use the default browser stack + + get "/embed/widget", PageController, :widget + end + + scope "/api", LSGWeb do + pipe_through :api + get "/icecast.json", PageController, :icecast + end +end diff --git a/lib/lsg_web/templates/layout/app.html.eex b/lib/lsg_web/templates/layout/app.html.eex new file mode 100644 index 0000000..0d91f12 --- /dev/null +++ b/lib/lsg_web/templates/layout/app.html.eex @@ -0,0 +1,35 @@ +<!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>Hello LSG!</title> + <link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>"> + </head> + + <body> + <div class="container"> + <header class="header"> + <nav role="navigation"> + <ul class="nav nav-pills pull-right"> + <li><a href="http://www.phoenixframework.org/docs">Get Started</a></li> + </ul> + </nav> + <span class="logo"></span> + </header> + + <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p> + <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p> + + <main role="main"> + <%= render @view_module, @view_template, assigns %> + </main> + + </div> <!-- /container --> + <script src="<%= static_path(@conn, "/js/app.js") %>"></script> + </body> +</html> diff --git a/lib/lsg_web/templates/page/index.html.eex b/lib/lsg_web/templates/page/index.html.eex new file mode 100644 index 0000000..0988ea5 --- /dev/null +++ b/lib/lsg_web/templates/page/index.html.eex @@ -0,0 +1,36 @@ +<div class="jumbotron"> + <h2><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h2> + <p class="lead">A productive web framework that<br />does not compromise speed and maintainability.</p> +</div> + +<div class="row marketing"> + <div class="col-lg-6"> + <h4>Resources</h4> + <ul> + <li> + <a href="http://phoenixframework.org/docs/overview">Guides</a> + </li> + <li> + <a href="https://hexdocs.pm/phoenix">Docs</a> + </li> + <li> + <a href="https://github.com/phoenixframework/phoenix">Source</a> + </li> + </ul> + </div> + + <div class="col-lg-6"> + <h4>Help</h4> + <ul> + <li> + <a href="http://groups.google.com/group/phoenix-talk">Mailing list</a> + </li> + <li> + <a href="http://webchat.freenode.net/?channels=elixir-lang">#elixir-lang on freenode IRC</a> + </li> + <li> + <a href="https://twitter.com/elixirphoenix">@elixirphoenix</a> + </li> + </ul> + </div> +</div> diff --git a/lib/lsg_web/templates/page/widget.html.eex b/lib/lsg_web/templates/page/widget.html.eex new file mode 100644 index 0000000..efa382f --- /dev/null +++ b/lib/lsg_web/templates/page/widget.html.eex @@ -0,0 +1,11 @@ +<!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"> +<link rel="stylesheet" href="<%= static_path(@conn, "/assets/css/widget.css") %>"> +<script src="<%= static_path(@conn, "/assets/js/widget.js") %>"></script> +</head> +<body></body> +</html> diff --git a/lib/lsg_web/views/error_helpers.ex b/lib/lsg_web/views/error_helpers.ex new file mode 100644 index 0000000..47906f2 --- /dev/null +++ b/lib/lsg_web/views/error_helpers.ex @@ -0,0 +1,40 @@ +defmodule LSGWeb.ErrorHelpers do + @moduledoc """ + Conveniences for translating and building error messages. + """ + + use Phoenix.HTML + + @doc """ + Generates tag for inlined form input errors. + """ + def error_tag(form, field) do + Enum.map(Keyword.get_values(form.errors, field), fn (error) -> + content_tag :span, translate_error(error), class: "help-block" + end) + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # Because error messages were defined within Ecto, we must + # call the Gettext module passing our Gettext backend. We + # also use the "errors" domain as translations are placed + # in the errors.po file. + # Ecto will pass the :count keyword if the error message is + # meant to be pluralized. + # On your own code and templates, depending on whether you + # need the message to be pluralized or not, this could be + # written simply as: + # + # dngettext "errors", "1 file", "%{count} files", count + # dgettext "errors", "is invalid" + # + if count = opts[:count] do + Gettext.dngettext(LSGWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(LSGWeb.Gettext, "errors", msg, opts) + end + end +end diff --git a/lib/lsg_web/views/error_view.ex b/lib/lsg_web/views/error_view.ex new file mode 100644 index 0000000..1a7a92d --- /dev/null +++ b/lib/lsg_web/views/error_view.ex @@ -0,0 +1,17 @@ +defmodule LSGWeb.ErrorView do + use LSGWeb, :view + + def render("404.html", _assigns) do + "Page not found" + end + + def render("500.html", _assigns) do + "Internal server 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 diff --git a/lib/lsg_web/views/layout_view.ex b/lib/lsg_web/views/layout_view.ex new file mode 100644 index 0000000..39216ed --- /dev/null +++ b/lib/lsg_web/views/layout_view.ex @@ -0,0 +1,3 @@ +defmodule LSGWeb.LayoutView do + use LSGWeb, :view +end diff --git a/lib/lsg_web/views/page_view.ex b/lib/lsg_web/views/page_view.ex new file mode 100644 index 0000000..90c384c --- /dev/null +++ b/lib/lsg_web/views/page_view.ex @@ -0,0 +1,3 @@ +defmodule LSGWeb.PageView do + use LSGWeb, :view +end |