summaryrefslogtreecommitdiff
path: root/lib/nola_web/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'lib/nola_web/controllers')
-rw-r--r--lib/nola_web/controllers/alcoolog_controller.ex323
-rw-r--r--lib/nola_web/controllers/gpt_controller.ex33
-rw-r--r--lib/nola_web/controllers/icecast_see_controller.ex41
-rw-r--r--lib/nola_web/controllers/irc_auth_sse_controller.ex66
-rw-r--r--lib/nola_web/controllers/irc_controller.ex101
-rw-r--r--lib/nola_web/controllers/network_controller.ex11
-rw-r--r--lib/nola_web/controllers/open_id_controller.ex64
-rw-r--r--lib/nola_web/controllers/page_controller.ex53
-rw-r--r--lib/nola_web/controllers/sms_controller.ex10
-rw-r--r--lib/nola_web/controllers/untappd_controller.ex18
10 files changed, 720 insertions, 0 deletions
diff --git a/lib/nola_web/controllers/alcoolog_controller.ex b/lib/nola_web/controllers/alcoolog_controller.ex
new file mode 100644
index 0000000..3081762
--- /dev/null
+++ b/lib/nola_web/controllers/alcoolog_controller.ex
@@ -0,0 +1,323 @@
+defmodule NolaWeb.AlcoologController do
+ use NolaWeb, :controller
+ require Logger
+
+ plug NolaWeb.ContextPlug when action not in [:token]
+ plug NolaWeb.ContextPlug, [restrict: :public] when action in [:token]
+
+ def token(conn, %{"token" => token}) do
+ case Nola.Token.lookup(token) do
+ {:ok, {:alcoolog, :index, network, channel}} -> index(conn, nil, network, channel)
+ err ->
+ Logger.debug("AlcoologControler: token #{inspect err} invalid")
+ conn
+ |> put_status(404)
+ |> text("Page not found")
+ end
+ end
+
+ def nick(conn = %{assigns: %{account: account}}, params = %{"network" => network, "nick" => nick}) do
+ profile_account = IRC.Account.find_always_by_nick(network, nick, nick)
+ days = String.to_integer(Map.get(params, "days", "180"))
+ friend? = Enum.member?(IRC.Membership.friends(account), profile_account.id)
+ if friend? do
+ stats = Nola.IRC.AlcoologPlugin.get_full_statistics(profile_account.id)
+ history = for {{nick, ts}, points, active, cl, deg, type, descr, meta} <- Nola.IRC.AlcoologPlugin.nick_history(profile_account) do
+ %{
+ at: ts |> DateTime.from_unix!(:millisecond),
+ points: points,
+ active: active,
+ cl: cl,
+ deg: deg,
+ type: type,
+ description: descr,
+ meta: meta
+ }
+ end
+ history = Enum.sort(history, &(DateTime.compare(&1.at, &2.at) != :lt))
+ |> IO.inspect()
+ conn
+ |> assign(:title, "alcoolog #{nick}")
+ |> render("user.html", network: network, profile: profile_account, days: days, nick: nick, history: history, stats: stats)
+ else
+ conn
+ |> put_status(404)
+ |> text("Page not found")
+ end
+ end
+
+ def nick_stats_json(conn = %{assigns: %{account: account}}, params = %{"network" => network, "nick" => nick}) do
+ profile_account = IRC.Account.find_always_by_nick(network, nick, nick)
+ friend? = Enum.member?(IRC.Membership.friends(account), profile_account.id)
+ if friend? do
+ stats = Nola.IRC.AlcoologPlugin.get_full_statistics(profile_account.id)
+
+ conn
+ |> put_resp_content_type("application/json")
+ |> text(Jason.encode!(stats))
+ else
+ conn
+ |> put_status(404)
+ |> json([])
+ end
+ end
+
+ def nick_gls_json(conn = %{assigns: %{account: account}}, params = %{"network" => network, "nick" => nick}) do
+ profile_account = IRC.Account.find_always_by_nick(network, nick, nick)
+ friend? = Enum.member?(IRC.Membership.friends(account), profile_account.id)
+ count = String.to_integer(Map.get(params, "days", "180"))
+ if friend? do
+ data = Nola.IRC.AlcoologPlugin.user_over_time_gl(profile_account, count)
+ delay = count*((24 * 60)*60)
+ now = DateTime.utc_now()
+ start_date = DateTime.utc_now()
+ |> DateTime.shift_zone!("Europe/Paris", Tzdata.TimeZoneDatabase)
+ |> DateTime.add(-delay, :second, Tzdata.TimeZoneDatabase)
+ |> DateTime.to_date()
+ |> Date.to_erl()
+ filled = (:calendar.date_to_gregorian_days(start_date) .. :calendar.date_to_gregorian_days(DateTime.utc_now |> DateTime.to_date() |> Date.to_erl))
+ |> Enum.to_list
+ |> Enum.map(&(:calendar.gregorian_days_to_date(&1)))
+ |> Enum.map(&Date.from_erl!(&1))
+ |> Enum.map(fn(date) ->
+ %{date: date, gls: Map.get(data, date, 0)}
+ end)
+ |> Enum.sort(&(Date.compare(&1.date, &2.date) != :gt))
+
+ conn
+ |> put_resp_content_type("application/json")
+ |> text(Jason.encode!(filled))
+ else
+ conn
+ |> put_status(404)
+ |> json([])
+ end
+ end
+
+
+
+ def nick_volumes_json(conn = %{assigns: %{account: account}}, params = %{"network" => network, "nick" => nick}) do
+ profile_account = IRC.Account.find_always_by_nick(network, nick, nick)
+ friend? = Enum.member?(IRC.Membership.friends(account), profile_account.id)
+ count = String.to_integer(Map.get(params, "days", "180"))
+ if friend? do
+ data = Nola.IRC.AlcoologPlugin.user_over_time(profile_account, count)
+ delay = count*((24 * 60)*60)
+ now = DateTime.utc_now()
+ start_date = DateTime.utc_now()
+ |> DateTime.shift_zone!("Europe/Paris", Tzdata.TimeZoneDatabase)
+ |> DateTime.add(-delay, :second, Tzdata.TimeZoneDatabase)
+ |> DateTime.to_date()
+ |> Date.to_erl()
+ filled = (:calendar.date_to_gregorian_days(start_date) .. :calendar.date_to_gregorian_days(DateTime.utc_now |> DateTime.to_date() |> Date.to_erl))
+ |> Enum.to_list
+ |> Enum.map(&(:calendar.gregorian_days_to_date(&1)))
+ |> Enum.map(&Date.from_erl!(&1))
+ |> Enum.map(fn(date) ->
+ %{date: date, volumes: Map.get(data, date, 0)}
+ end)
+ |> Enum.sort(&(Date.compare(&1.date, &2.date) != :gt))
+
+ conn
+ |> put_resp_content_type("application/json")
+ |> text(Jason.encode!(filled))
+ else
+ conn
+ |> put_status(404)
+ |> json([])
+ end
+ end
+
+ def nick_log_json(conn = %{assigns: %{account: account}}, %{"network" => network, "nick" => nick}) do
+ profile_account = IRC.Account.find_always_by_nick(network, nick, nick)
+ friend? = Enum.member?(IRC.Membership.friends(account), profile_account.id)
+ if friend? do
+ history = for {{nick, ts}, points, active, cl, deg, type, descr, meta} <- Nola.IRC.AlcoologPlugin.nick_history(profile_account) do
+ %{
+ at: ts |> DateTime.from_unix!(:millisecond) |> DateTime.to_iso8601(),
+ points: points,
+ active: active,
+ cl: cl,
+ deg: deg,
+ type: type,
+ description: descr,
+ meta: meta
+ }
+ end
+ last = List.last(history)
+ {_, active} = Nola.IRC.AlcoologPlugin.user_stats(profile_account)
+ last = %{last | active: active, at: DateTime.utc_now() |> DateTime.to_iso8601()}
+ history = history ++ [last]
+
+ conn
+ |> put_resp_content_type("application/json")
+ |> text(Jason.encode!(history))
+ else
+ conn
+ |> put_status(404)
+ |> json([])
+ end
+ end
+
+ def nick_history_json(conn = %{assigns: %{account: account}}, %{"network" => network, "nick" => nick}) do
+ profile_account = IRC.Account.find_always_by_nick(network, nick, nick)
+ friend? = Enum.member?(IRC.Membership.friends(account), profile_account.id)
+ if friend? do
+ history = for {_, date, value} <- Nola.IRC.AlcoologAnnouncerPlugin.log(profile_account) do
+ %{date: DateTime.to_iso8601(date), value: value}
+ end
+ conn
+ |> put_resp_content_type("application/json")
+ |> text(Jason.encode!(history))
+ else
+ conn
+ |> put_status(404)
+ |> json([])
+ end
+ end
+
+ def index(conn = %{assigns: %{account: account}}, %{"network" => network, "chan" => channel}) do
+ index(conn, account, network, NolaWeb.reformat_chan(channel))
+ end
+
+ def index(conn = %{assigns: %{account: account}}, _) do
+ index(conn, account, nil, nil)
+ end
+
+ #def index(conn, params) do
+ # network = Map.get(params, "network")
+ # chan = if c = Map.get(params, "chan") do
+ # NolaWeb.reformat_chan(c)
+ # end
+ # irc_conn = if network do
+ # IRC.Connection.get_network(network, chan)
+ # end
+ # bot = if(irc_conn, do: irc_conn.nick)#
+ #
+ # conn
+ # |> put_status(403)
+ # |> render("auth.html", network: network, channel: chan, irc_conn: conn, bot: bot)
+ #end
+
+ def index(conn, account, network, channel) do
+ aday = ((24 * 60)*60)
+ now = DateTime.utc_now()
+ before7 = now
+ |> DateTime.add(-(7*aday), :second)
+ |> DateTime.to_unix(:millisecond)
+ before15 = now
+ |> DateTime.add(-(15*aday), :second)
+ |> DateTime.to_unix(:millisecond)
+ before31 = now
+ |> DateTime.add(-(31*aday), :second)
+ |> DateTime.to_unix(:millisecond)
+ #match = :ets.fun2ms(fn(obj = {{^nick, date}, _, _, _, _}) when date > before -> obj end)
+ match = [
+ {{{:_, :"$1"}, :_, :_, :_, :_, :_, :_, :_},
+ [
+ {:>, :"$1", {:const, before15}},
+ ], [:"$_"]}
+ ]
+
+ # tuple ets: {{nick, date}, volumes, current, nom, commentaire}
+ members = IRC.Membership.expanded_members_or_friends(account, network, channel)
+ members_ids = Enum.map(members, fn({account, _, nick}) -> account.id end)
+ member_names = Enum.reduce(members, %{}, fn({account, _, nick}, acc) -> Map.put(acc, account.id, nick) end)
+ drinks = :ets.select(Nola.IRC.AlcoologPlugin.ETS, match)
+ |> Enum.filter(fn({{account, _}, _vol, _cur, _cl, _deg, _name, _cmt, _meta}) -> Enum.member?(members_ids, account) end)
+ |> Enum.map(fn({{account, _}, _, _, _, _, _, _, _} = object) -> {object, Map.get(member_names, account)} end)
+ |> Enum.sort_by(fn({{{_, ts}, _, _, _, _, _, _, _}, _}) -> ts end, &>/2)
+
+ stats = Nola.IRC.AlcoologPlugin.get_channel_statistics(account, network, channel)
+
+ top = Enum.reduce(drinks, %{}, fn({{{account_id, _}, vol, _, _, _, _, _, _}, _}, acc) ->
+ nick = Map.get(member_names, account_id)
+ all = Map.get(acc, nick, 0)
+ Map.put(acc, nick, all + vol)
+ end)
+ |> Enum.sort_by(fn({_nick, count}) -> count end, &>/2)
+ # {date, single_peak}
+ #
+ conn
+ |> assign(:title, "alcoolog")
+ |> render("index.html", network: network, channel: channel, drinks: drinks, top: top, stats: stats)
+ end
+
+ def index_gls_json(conn = %{assigns: %{account: account}}, %{"network" => network, "chan" => channel}) do
+ count = 30
+ channel = NolaWeb.reformat_chan(channel)
+ members = IRC.Membership.expanded_members_or_friends(account, network, channel)
+ members_ids = Enum.map(members, fn({account, _, nick}) -> account.id end)
+ member_names = Enum.reduce(members, %{}, fn({account, _, nick}, acc) -> Map.put(acc, account.id, nick) end)
+ delay = count*((24 * 60)*60)
+ now = DateTime.utc_now()
+ start_date = DateTime.utc_now()
+ |> DateTime.shift_zone!("Europe/Paris", Tzdata.TimeZoneDatabase)
+ |> DateTime.add(-delay, :second, Tzdata.TimeZoneDatabase)
+ |> DateTime.to_date()
+ |> Date.to_erl()
+ filled = (:calendar.date_to_gregorian_days(start_date) .. :calendar.date_to_gregorian_days(DateTime.utc_now |> DateTime.to_date() |> Date.to_erl))
+ |> Enum.to_list
+ |> Enum.map(&(:calendar.gregorian_days_to_date(&1)))
+ |> Enum.map(&Date.from_erl!(&1))
+ |> Enum.map(fn(date) ->
+ {date, (for {a, _, _} <- members, into: Map.new, do: {Map.get(member_names, a.id, a.id), 0})}
+ end)
+ |> Enum.into(Map.new)
+
+ gls = Enum.reduce(members, filled, fn({account, _, _}, gls) ->
+ Enum.reduce(Nola.IRC.AlcoologPlugin.user_over_time_gl(account, count), gls, fn({date, gl}, gls) ->
+ u = Map.get(gls, date, %{})
+ |> Map.put(Map.get(member_names, account.id, account.id), gl)
+ Map.put(gls, date, u)
+ end)
+ end)
+
+ dates = (:calendar.date_to_gregorian_days(start_date) .. :calendar.date_to_gregorian_days(DateTime.utc_now |> DateTime.to_date() |> Date.to_erl))
+ |> Enum.to_list
+ |> Enum.map(&(:calendar.gregorian_days_to_date(&1)))
+ |> Enum.map(&Date.from_erl!(&1))
+
+ filled2 = Enum.map(member_names, fn({_, name}) ->
+ history = (:calendar.date_to_gregorian_days(start_date) .. :calendar.date_to_gregorian_days(DateTime.utc_now |> DateTime.to_date() |> Date.to_erl))
+ |> Enum.to_list
+ |> Enum.map(&(:calendar.gregorian_days_to_date(&1)))
+ |> Enum.map(&Date.from_erl!(&1))
+ |> Enum.map(fn(date) ->
+ get_in(gls, [date, name]) #%{date: date, gl: get_in(gls, [date, name])}
+ end)
+ if Enum.all?(history, fn(x) -> x == 0 end) do
+ nil
+ else
+ %{name: name, history: history}
+ end
+ end)
+ |> Enum.filter(fn(x) -> x end)
+
+ conn
+ |> put_resp_content_type("application/json")
+ |> text(Jason.encode!(%{labels: dates, data: filled2}))
+ end
+
+ def minisync(conn, %{"user_id" => user_id, "key" => key, "value" => value}) do
+ account = IRC.Account.get(user_id)
+ if account do
+ ds = Nola.IRC.AlcoologPlugin.data_state()
+ meta = Nola.IRC.AlcoologPlugin.get_user_meta(ds, account.id)
+ case Float.parse(value) do
+ {val, _} ->
+ new_meta = Map.put(meta, String.to_existing_atom(key), val)
+ Nola.IRC.AlcoologPlugin.put_user_meta(ds, account.id, new_meta)
+ _ ->
+ conn
+ |> put_status(:unprocessable_entity)
+ |> text("invalid value")
+ end
+ else
+ conn
+ |> put_status(:not_found)
+ |> text("not found")
+ end
+ end
+
+end
diff --git a/lib/nola_web/controllers/gpt_controller.ex b/lib/nola_web/controllers/gpt_controller.ex
new file mode 100644
index 0000000..038b235
--- /dev/null
+++ b/lib/nola_web/controllers/gpt_controller.ex
@@ -0,0 +1,33 @@
+defmodule NolaWeb.GptController do
+ use NolaWeb, :controller
+ require Logger
+
+ plug NolaWeb.ContextPlug
+
+ def result(conn, params = %{"id" => result_id}) do
+ case Nola.IRC.GptPlugin.get_result(result_id) do
+ {:ok, result} ->
+ network = Map.get(params, "network")
+ channel = if c = Map.get(params, "chan"), do: NolaWeb.reformat_chan(c)
+ render(conn, "result.html", network: network, channel: channel, result: result)
+ {:error, :not_found} ->
+ conn
+ |> put_status(404)
+ |> text("Page not found")
+ end
+ end
+
+ def prompt(conn, params = %{"id" => prompt_id}) do
+ case Nola.IRC.GptPlugin.get_prompt(prompt_id) do
+ {:ok, prompt} ->
+ network = Map.get(params, "network")
+ channel = if c = Map.get(params, "chan"), do: NolaWeb.reformat_chan(c)
+ render(conn, "prompt.html", network: network, channel: channel, prompt: prompt)
+ {:error, :not_found} ->
+ conn
+ |> put_status(404)
+ |> text("Page not found")
+ end
+ end
+
+end
diff --git a/lib/nola_web/controllers/icecast_see_controller.ex b/lib/nola_web/controllers/icecast_see_controller.ex
new file mode 100644
index 0000000..877ad4e
--- /dev/null
+++ b/lib/nola_web/controllers/icecast_see_controller.ex
@@ -0,0 +1,41 @@
+defmodule NolaWeb.IcecastSseController do
+ use NolaWeb, :controller
+ require Logger
+
+ @ping_interval 20_000
+
+ def sse(conn, _params) do
+ conn
+ |> put_resp_header("X-Accel-Buffering", "no")
+ |> put_resp_header("content-type", "text/event-stream")
+ |> send_chunked(200)
+ |> subscribe
+ |> send_sse_message("ping", "ping")
+ |> send_sse_message("icecast", Nola.IcecastAgent.get)
+ |> sse_loop
+ end
+
+ def subscribe(conn) do
+ :timer.send_interval(@ping_interval, {:event, :ping})
+ {:ok, _} = Registry.register(Nola.BroadcastRegistry, "icecast", [])
+ conn
+ end
+
+ def sse_loop(conn) do
+ {type, event} = receive do
+ {:event, :ping} -> {"ping", "ping"}
+ {:icecast, stats} -> {"icecast", stats}
+ end
+
+ conn
+ |> send_sse_message(type, event)
+ |> sse_loop()
+ end
+
+ defp send_sse_message(conn, type, data) do
+ json = Jason.encode!(%{type => data})
+ {:ok, conn} = chunk(conn, "event: #{type}\ndata: #{json}\n\n")
+ conn
+ end
+
+end
diff --git a/lib/nola_web/controllers/irc_auth_sse_controller.ex b/lib/nola_web/controllers/irc_auth_sse_controller.ex
new file mode 100644
index 0000000..62ee2b5
--- /dev/null
+++ b/lib/nola_web/controllers/irc_auth_sse_controller.ex
@@ -0,0 +1,66 @@
+defmodule NolaWeb.IrcAuthSseController do
+ use NolaWeb, :controller
+ require Logger
+
+ @ping_interval 20_000
+ @expire_delay :timer.minutes(3)
+
+ def sse(conn, params) do
+ perks = if uri = Map.get(params, "redirect_to") do
+ {:redirect, uri}
+ else
+ nil
+ end
+ token = String.downcase(EntropyString.random_string(65))
+ conn
+ |> assign(:token, token)
+ |> assign(:perks, perks)
+ |> put_resp_header("X-Accel-Buffering", "no")
+ |> put_resp_header("content-type", "text/event-stream")
+ |> send_chunked(200)
+ |> subscribe()
+ |> send_sse_message("token", token)
+ |> sse_loop
+ end
+
+ def subscribe(conn) do
+ :timer.send_interval(@ping_interval, {:event, :ping})
+ :timer.send_after(@expire_delay, {:event, :expire})
+ {:ok, _} = Registry.register(IRC.PubSub, "messages:private", [])
+ conn
+ end
+
+ def sse_loop(conn) do
+ {type, event, exit} = receive do
+ {:event, :ping} -> {"ping", "ping", false}
+ {:event, :expire} -> {"expire", "expire", true}
+ {:irc, :text, %{account: account, text: token} = m} ->
+ if String.downcase(String.trim(token)) == conn.assigns.token do
+ path = Nola.AuthToken.new_path(account.id, conn.assigns.perks)
+ m.replyfun.("ok!")
+ {"authenticated", path, true}
+ else
+ {nil, nil, false}
+ end
+ _ -> {nil, nil, false}
+ end
+
+ conn = if type do
+ send_sse_message(conn, type, event)
+ else
+ conn
+ end
+
+ if exit do
+ conn
+ else
+ sse_loop(conn)
+ end
+ end
+
+ defp send_sse_message(conn, type, data) do
+ {:ok, conn} = chunk(conn, "event: #{type}\ndata: #{data}\n\n")
+ conn
+ end
+
+end
diff --git a/lib/nola_web/controllers/irc_controller.ex b/lib/nola_web/controllers/irc_controller.ex
new file mode 100644
index 0000000..c617e78
--- /dev/null
+++ b/lib/nola_web/controllers/irc_controller.ex
@@ -0,0 +1,101 @@
+defmodule NolaWeb.IrcController do
+ use NolaWeb, :controller
+
+ plug NolaWeb.ContextPlug
+
+ def index(conn, params) do
+ network = Map.get(params, "network")
+ channel = if c = Map.get(params, "chan"), do: NolaWeb.reformat_chan(c)
+ commands = for mod <- Enum.uniq([IRC.Account.AccountPlugin] ++ IRC.Plugin.enabled()) do
+ if is_atom(mod) do
+ identifier = Module.split(mod) |> List.last |> String.replace("Plugin", "") |> Macro.underscore
+ {identifier, mod.irc_doc()}
+ end
+ end
+ |> Enum.filter(& &1)
+ |> Enum.filter(fn({_, doc}) -> doc end)
+ members = cond do
+ network && channel -> Enum.map(IRC.UserTrack.channel(network, channel), fn(tuple) -> IRC.UserTrack.User.from_tuple(tuple) end)
+ true ->
+ IRC.Membership.of_account(conn.assigns.account)
+ end
+ render conn, "index.html", network: network, commands: commands, channel: channel, members: members
+ end
+
+ def txt(conn, %{"name" => name}) do
+ if String.contains?(name, ".txt") do
+ name = String.replace(name, ".txt", "")
+ data = data()
+ if Map.has_key?(data, name) do
+ lines = Enum.join(data[name], "\n")
+ text(conn, lines)
+ else
+ conn
+ |> put_status(404)
+ |> text("Not found")
+ end
+ else
+ do_txt(conn, name)
+ end
+ end
+ def txt(conn, _), do: do_txt(conn, nil)
+
+
+ defp do_txt(conn, nil) do
+ doc = Nola.IRC.TxtPlugin.irc_doc()
+ data = data()
+ main = Enum.filter(data, fn({trigger, _}) -> !String.contains?(trigger, ".") end) |> Enum.into(Map.new)
+ system = Enum.filter(data, fn({trigger, _}) -> String.contains?(trigger, ".") end) |> Enum.into(Map.new)
+ lines = Enum.reduce(main, 0, fn({_, lines}, acc) -> acc + Enum.count(lines) end)
+ conn
+ |> assign(:title, "txt")
+ |> render("txts.html", data: main, doc: doc, files: Enum.count(main), lines: lines, system: system)
+ end
+
+ defp do_txt(conn, txt) do
+ data = data()
+ base_url = cond do
+ conn.assigns[:chan] -> "/#{conn.assigns.network}/#{NolaWeb.format_chan(conn.assigns.chan)}"
+ true -> "/-"
+ end
+ if lines = Map.get(data, txt) do
+ lines = Enum.map(lines, fn(line) ->
+ line
+ |> String.split("\\\\")
+ |> Enum.intersperse(Phoenix.HTML.Tag.tag(:br))
+ end)
+ conn
+ |> assign(:breadcrumbs, [{"txt", "#{base_url}/txt"}])
+ |> assign(:title, "#{txt}.txt")
+ |> render("txt.html", name: txt, data: lines, doc: nil)
+ else
+ conn
+ |> put_status(404)
+ |> text("Not found")
+ end
+ end
+
+ defp data() do
+ dir = Application.get_env(:nola, :data_path) <> "/irc.txt/"
+ Path.wildcard(dir <> "/*.txt")
+ |> Enum.reduce(%{}, fn(path, m) ->
+ path = String.split(path, "/")
+ file = List.last(path)
+ key = String.replace(file, ".txt", "")
+ data = dir <> file
+ |> File.read!
+ |> String.split("\n")
+ |> Enum.reject(fn(line) ->
+ cond do
+ line == "" -> true
+ !line -> true
+ true -> false
+ end
+ end)
+ Map.put(m, key, data)
+ end)
+ |> Enum.sort
+ |> Enum.into(Map.new)
+ end
+
+end
diff --git a/lib/nola_web/controllers/network_controller.ex b/lib/nola_web/controllers/network_controller.ex
new file mode 100644
index 0000000..800294f
--- /dev/null
+++ b/lib/nola_web/controllers/network_controller.ex
@@ -0,0 +1,11 @@
+defmodule NolaWeb.NetworkController do
+ use NolaWeb, :controller
+ plug NolaWeb.ContextPlug
+
+ def index(conn, %{"network" => network}) do
+ conn
+ |> assign(:title, network)
+ |> render("index.html")
+ end
+
+end
diff --git a/lib/nola_web/controllers/open_id_controller.ex b/lib/nola_web/controllers/open_id_controller.ex
new file mode 100644
index 0000000..d3fef5d
--- /dev/null
+++ b/lib/nola_web/controllers/open_id_controller.ex
@@ -0,0 +1,64 @@
+defmodule NolaWeb.OpenIdController do
+ use NolaWeb, :controller
+ plug NolaWeb.ContextPlug, restrict: :public
+ require Logger
+
+ def login(conn, _) do
+ url = OAuth2.Client.authorize_url!(new_client(), scope: "openid", state: Base.url_encode64(:crypto.strong_rand_bytes(32), padding: false))
+ redirect(conn, external: url)
+ end
+
+ def callback(conn, %{"error" => error_code, "error_description" => error}) do
+ Logger.warn("OpenId error: #{error_code} #{error}")
+ render(conn, "error.html", error: error)
+ end
+
+ def callback(conn, %{"code" => code, "state" => state}) do
+ with \
+ client = %{token: %OAuth2.AccessToken{access_token: json}} = OAuth2.Client.get_token!(new_client(), state: state, code: code),
+ {:ok, %{"access_token" => token}} <- Jason.decode(json),
+ client = %OAuth2.Client{client | token: %OAuth2.AccessToken{access_token: token}},
+ {:ok, %OAuth2.Response{body: body}} <- OAuth2.Client.get(client, "/userinfo"),
+ {:ok, %{"sub" => id, "preferred_username" => username}} <- Jason.decode(body)
+ do
+ if account = conn.assigns.account do
+ if !IRC.Account.get_meta(account, "identity-id") do # XXX: And oidc id not linked yet
+ IRC.Account.put_meta(account, "identity-id", id)
+ end
+ IRC.Account.put_meta(account, "identity-username", username)
+ conn
+ else
+ conn
+ end
+
+ conn
+ |> put_session(:oidc_id, id)
+ |> put_flash(:info, "Logged in!")
+ |> redirect(to: Routes.path(conn, "/"))
+ else
+ {:error, %OAuth2.Response{status_code: 401}} ->
+ Logger.error("OpenID: Unauthorized token")
+ render(conn, "error.html", error: "The token is invalid.")
+ {:error, %OAuth2.Error{reason: reason}} ->
+ Logger.error("Error: #{inspect reason}")
+ render(conn, "error.html", error: reason)
+ end
+ end
+
+ def callback(conn, _params) do
+ render(conn, "error.html", error: "Unspecified error.")
+ end
+
+ defp new_client() do
+ config = Application.get_env(:nola, :oidc)
+ OAuth2.Client.new([
+ strategy: OAuth2.Strategy.AuthCode,
+ client_id: config[:client_id],
+ client_secret: config[:client_secret],
+ site: config[:base_url],
+ authorize_url: config[:authorize_url],
+ token_url: config[:token_url],
+ redirect_uri: Routes.open_id_url(NolaWeb.Endpoint, :callback)
+ ])
+ end
+end
diff --git a/lib/nola_web/controllers/page_controller.ex b/lib/nola_web/controllers/page_controller.ex
new file mode 100644
index 0000000..2ac4d0a
--- /dev/null
+++ b/lib/nola_web/controllers/page_controller.ex
@@ -0,0 +1,53 @@
+defmodule NolaWeb.PageController do
+ use NolaWeb, :controller
+
+ plug NolaWeb.ContextPlug when action not in [:token]
+ plug NolaWeb.ContextPlug, [restrict: :public] when action in [:token]
+
+ def token(conn, %{"token" => token}) do
+ with \
+ {:ok, account, perks} <- Nola.AuthToken.lookup(token)
+ do
+ IO.puts("Authenticated account #{inspect account}")
+ conn = put_session(conn, :account, account)
+ case perks do
+ nil -> redirect(conn, to: "/")
+ {:redirect, path} -> redirect(conn, to: path)
+ {:external_redirect, url} -> redirect(conn, external: url)
+ end
+ else
+ z ->
+ IO.inspect(z)
+ text(conn, "Error: invalid or expired token")
+ end
+ end
+
+ def index(conn = %{assigns: %{account: account}}, _) do
+ memberships = IRC.Membership.of_account(account)
+ users = IRC.UserTrack.find_by_account(account)
+ metas = IRC.Account.get_all_meta(account)
+ predicates = IRC.Account.get_predicates(account)
+ conn
+ |> assign(:title, account.name)
+ |> render("user.html", users: users, memberships: memberships, metas: metas, predicates: predicates)
+ end
+
+ def irc(conn, _) do
+ bot_helps = for mod <- Nola.IRC.env(:handlers) do
+ mod.irc_doc()
+ end
+ render conn, "irc.html", bot_helps: bot_helps
+ end
+
+ def authenticate(conn, _) do
+ with \
+ {:account, account_id} when is_binary(account_id) <- {:account, get_session(conn, :account)},
+ {:account, account} when not is_nil(account) <- {:account, IRC.Account.get(account_id)}
+ do
+ assign(conn, :account, account)
+ else
+ _ -> conn
+ end
+ end
+
+end
diff --git a/lib/nola_web/controllers/sms_controller.ex b/lib/nola_web/controllers/sms_controller.ex
new file mode 100644
index 0000000..575655c
--- /dev/null
+++ b/lib/nola_web/controllers/sms_controller.ex
@@ -0,0 +1,10 @@
+defmodule NolaWeb.SmsController do
+ use NolaWeb, :controller
+ require Logger
+
+ def ovh_callback(conn, %{"senderid" => from, "message" => message}) do
+ spawn(fn() -> Nola.IRC.SmsPlugin.incoming(from, String.trim(message)) end)
+ text(conn, "")
+ end
+
+end
diff --git a/lib/nola_web/controllers/untappd_controller.ex b/lib/nola_web/controllers/untappd_controller.ex
new file mode 100644
index 0000000..d3a540d
--- /dev/null
+++ b/lib/nola_web/controllers/untappd_controller.ex
@@ -0,0 +1,18 @@
+defmodule NolaWeb.UntappdController do
+ use NolaWeb, :controller
+
+ def callback(conn, %{"code" => code}) do
+ with \
+ {:account, account_id} when is_binary(account_id) <- {:account, get_session(conn, :account)},
+ {:account, account} when not is_nil(account) <- {:account, IRC.Account.get(account_id)},
+ {:ok, auth_token} <- Untappd.auth_callback(code)
+ do
+ IRC.Account.put_meta(account, "untappd-token", auth_token)
+ text(conn, "OK!")
+ else
+ {:account, _} -> text(conn, "Error: account not found")
+ :error -> text(conn, "Error: untappd authentication failed")
+ end
+ end
+
+end