From 03027baa11ac4fcf94e89ed00816eb15df4c3c5c Mon Sep 17 00:00:00 2001 From: Mikko Ahlroth Date: Fri, 20 Jan 2023 21:09:11 +0200 Subject: [PATCH] Initial commit --- .formatter.exs | 4 +++ .gitignore | 26 +++++++++++++++++ .tool-versions | 2 ++ README.md | 21 +++++++++++++ config/config.exs | 22 ++++++++++++++ lib/feeniks/application.ex | 19 ++++++++++++ lib/feeniks/controller.ex | 5 ++++ lib/feeniks/endpoint.ex | 58 ++++++++++++++++++++++++++++++++++++ lib/feeniks/router.ex | 60 ++++++++++++++++++++++++++++++++++++++ lib/plug_helpers.ex | 39 +++++++++++++++++++++++++ lib/router_helpers.ex | 7 +++++ mix.exs | 31 ++++++++++++++++++++ mix.lock | 18 ++++++++++++ priv/static/app.css | 1 + 14 files changed, 313 insertions(+) create mode 100644 .formatter.exs create mode 100644 .gitignore create mode 100644 .tool-versions create mode 100644 README.md create mode 100644 config/config.exs create mode 100644 lib/feeniks/application.ex create mode 100644 lib/feeniks/controller.ex create mode 100644 lib/feeniks/endpoint.ex create mode 100644 lib/feeniks/router.ex create mode 100644 lib/plug_helpers.ex create mode 100644 lib/router_helpers.ex create mode 100644 mix.exs create mode 100644 mix.lock create mode 100644 priv/static/app.css diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e604ba0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +feeniks-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..e6e768c --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +elixir 1.14.3-otp-25 +erlang 25.1.1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c0b3ca --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Feeniks + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `feeniks` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:feeniks, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..e45f845 --- /dev/null +++ b/config/config.exs @@ -0,0 +1,22 @@ +import Config + +# Configures the endpoint +config :feeniks, Feeniks.Endpoint, + render_errors: [accepts: ~w(html json)], + pubsub_server: Feeniks.PubSub, + code_reloader: config_env() == :dev, + debug_errors: config_env() == :dev, + check_origin: config_env() == :prod + +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + +# Do not print debug messages in production +if config_env() == :prod do + config :logger, level: :error +end diff --git a/lib/feeniks/application.ex b/lib/feeniks/application.ex new file mode 100644 index 0000000..aa98a14 --- /dev/null +++ b/lib/feeniks/application.ex @@ -0,0 +1,19 @@ +defmodule Feeniks.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + {Bandit, plug: Feeniks.Endpoint, scheme: :http, options: [port: 31545]} + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: Feeniks.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/lib/feeniks/controller.ex b/lib/feeniks/controller.ex new file mode 100644 index 0000000..80925f5 --- /dev/null +++ b/lib/feeniks/controller.ex @@ -0,0 +1,5 @@ +defmodule Feeniks.Controller do + def index(conn, params) do + Phoenix.Controller.text(conn, inspect(params)) + end +end diff --git a/lib/feeniks/endpoint.ex b/lib/feeniks/endpoint.ex new file mode 100644 index 0000000..9970440 --- /dev/null +++ b/lib/feeniks/endpoint.ex @@ -0,0 +1,58 @@ +defmodule Feeniks.Endpoint do + import PlugHelpers + + @behaviour Plug + + @config Phoenix.Endpoint.Supervisor.config(:feeniks, __MODULE__) + @code_reloading @config[:code_reloader] + + @session_options [ + store: :cookie, + key: "_feeniks_key", + signing_salt: "örgölbörgöl" + ] + + @plugs pipeline([ + &__MODULE__.set_endpoint/1, + module_plug(Plug.Static, + at: "/", + from: :feeniks, + gzip: true, + only: ~w(app.css favicon.ico robots.txt) + ), + if @code_reloading do + [ + module_plug(Phoenix.LiveReloader), + module_plug(Phoenix.CodeReloader) + ] + end, + module_plug(Plug.RequestId), + module_plug(Plug.Logger), + module_plug(Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Jason + ), + module_plug(Plug.MethodOverride), + module_plug(Plug.Head), + module_plug( + Plug.Session, + @session_options + ), + module_plug(Feeniks.Router) + ]) + + @impl Plug + def init(opts), do: opts + + @impl Plug + def call(conn, _opts) do + run_plugs(conn, @plugs) + end + + def config(key) do + @config[key] + end + + def set_endpoint(conn), do: Plug.Conn.put_private(conn, :phoenix_endpoint, __MODULE__) +end diff --git a/lib/feeniks/router.ex b/lib/feeniks/router.ex new file mode 100644 index 0000000..aa67d2d --- /dev/null +++ b/lib/feeniks/router.ex @@ -0,0 +1,60 @@ +defmodule Feeniks.Router do + import PlugHelpers + import RouterHelpers + + @behaviour Plug + + @common_browser pipeline([ + &Plug.Conn.fetch_session/1, + &Phoenix.Controller.fetch_flash/1, + &__MODULE__.put_root_layout/1, + &Phoenix.Controller.put_secure_browser_headers/1, + &Phoenix.Controller.protect_from_forgery/1 + ]) + + @common_api pipeline([ + &__MODULE__.accepts_json/1 + ]) + + @index @common_browser ++ [build_action(&Feeniks.Controller.index/1)] + + @impl Plug + def init(opts), do: opts + + @impl Plug + def call(conn, _opts) do + path_info = Plug.Router.Utils.decode_path_info!(conn) + match(conn, conn.method, path_info) + end + + def put_root_layout(conn) do + Phoenix.Controller.put_root_layout(conn, {Feeniks.LayoutView, :root}) + end + + def accepts_json(conn) do + Phoenix.Controller.accepts(conn, ["json"]) + end + + @spec match(Plug.Conn.t(), Plug.Conn.method(), Plug.Conn.segments()) :: Plug.Conn.t() + defp match(conn, method, path_info) + + defp match(conn, "GET", _) do + conn + |> common_browser() + |> Feeniks.Controller.index(conn.params) + end + + defp match(conn, "POST", _) do + conn + |> run_plugs(@common_api) + |> Feeniks.Controller.index(conn.params) + end + + defp match(conn, _, _) do + conn |> common_browser() |> Plug.Conn.put_status(200) |> Phoenix.Controller.text("Not found") + end + + defp common_browser(conn) do + run_plugs(conn, @common_browser) + end +end diff --git a/lib/plug_helpers.ex b/lib/plug_helpers.ex new file mode 100644 index 0000000..d40fd8e --- /dev/null +++ b/lib/plug_helpers.ex @@ -0,0 +1,39 @@ +defmodule PlugHelpers do + @typep plug_definition() :: {module(), term()} + @typep plug_function() :: (Plug.Conn.t() -> Plug.Conn.t()) + + @spec module_plug(module(), term()) :: plug_definition() + def module_plug(plug, opts \\ []) do + {plug, plug.init(opts)} + end + + @spec pipeline(list()) :: [plug_definition() | plug_function()] + def pipeline(plugs) do + plugs + |> List.flatten() + |> Enum.filter(fn + {mod, _opts} when is_atom(mod) -> true + fun when is_function(fun, 1) -> true + _ -> false + end) + end + + @spec run_plugs(Plug.Conn.t(), [plug_definition() | plug_function()]) :: + Plug.Conn.t() + def run_plugs(conn, plugs) do + for plug <- plugs, reduce: conn do + %Plug.Conn{halted: true} = c -> + c + + %Plug.Conn{} = c -> + case plug do + p when is_function(p) -> p.(c) + {p, opts} -> p.call(c, opts) + what -> raise "Unknown plug type #{inspect(what)} in plug list" + end + + other -> + "Expected plugs to return Plug.Conn, got: #{inspect(other)}" + end + end +end diff --git a/lib/router_helpers.ex b/lib/router_helpers.ex new file mode 100644 index 0000000..c64a391 --- /dev/null +++ b/lib/router_helpers.ex @@ -0,0 +1,7 @@ +defmodule RouterHelpers do + @spec build_action((Plug.Conn.t(), Plug.Conn.params() -> Plug.Conn.t())) :: + (Plug.Conn.t() -> Plug.Conn.t()) + def build_action(action) do + fn conn -> action.(conn, conn.params) end + end +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..63760c6 --- /dev/null +++ b/mix.exs @@ -0,0 +1,31 @@ +defmodule Feeniks.MixProject do + use Mix.Project + + def project do + [ + app: :feeniks, + version: "0.1.0", + elixir: "~> 1.14", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {Feeniks.Application, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:phoenix, "~> 1.6.15"}, + {:bandit, "~> 0.6.7"}, + {:jason, "~> 1.4"}, + {:phoenix_live_reload, "~> 1.4"} + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..0e99637 --- /dev/null +++ b/mix.lock @@ -0,0 +1,18 @@ +%{ + "bandit": {:hex, :bandit, "0.6.7", "8d768a512ecbda9bd4e71fe223fce8d57c30899ede61dc70a0d4d34407910a8e", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:thousand_island, "~> 0.5.14", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.4.3", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "0f1e9ac0e09714ee54c76001fe9d973aa25bff9cd058668c8f6cd0152f8ca3cf"}, + "castore": {:hex, :castore, "1.0.0", "c25cd0794c054ebe6908a86820c8b92b5695814479ec95eeff35192720b71eec", [:mix], [], "hexpm", "577d0e855983a97ca1dfa33cbb8a3b6ece6767397ffb4861514343b078fc284b"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, + "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, + "phoenix": {:hex, :phoenix, "1.6.15", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.0", "c57bc5044f25f007dc86ab21895688c098a9f846a8dda6bc40e2d0ddc146e38f", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1b066f99a26fd22064c12b2600a9a6e56700f591bf7b20b418054ea38b4d4357"}, + "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, + "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "thousand_island": {:hex, :thousand_island, "0.5.15", "3163c8b61c5e985a80e330d8544c4409e6039a1796587b812385051291b25361", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7347a922f7c8ae3f36737455c6539bba37e3e37c17cde20f9bac3fd0367a52f"}, + "websock": {:hex, :websock, "0.4.3", "184ac396bdcd3dfceb5b74c17d221af659dd559a95b1b92041ecb51c9b728093", [:mix], [], "hexpm", "5e4dd85f305f43fd3d3e25d70bec4a45228dfed60f0f3b072d8eddff335539cf"}, +} diff --git a/priv/static/app.css b/priv/static/app.css new file mode 100644 index 0000000..e63c05c --- /dev/null +++ b/priv/static/app.css @@ -0,0 +1 @@ +@charset "utf-8";