mirror of https://github.com/mitchell/shortnr.git
Project architecture boilerplate
This commit is contained in:
commit
7ff70d452a
|
@ -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").
|
||||||
|
service-*.tar
|
||||||
|
|
||||||
|
# Ignore elixir-ls artifacts
|
||||||
|
/.elixir_ls/
|
|
@ -0,0 +1,33 @@
|
||||||
|
FROM elixir:1.9-slim as build
|
||||||
|
|
||||||
|
WORKDIR /root/shortnr/service
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN mix local.hex --force
|
||||||
|
RUN mix local.rebar --force
|
||||||
|
|
||||||
|
RUN env MIX_ENV=prod mix release
|
||||||
|
|
||||||
|
FROM debian:buster-20191014-slim
|
||||||
|
|
||||||
|
WORKDIR /home/shortnr
|
||||||
|
COPY --from=build /root/shortnr/service/_build/prod/rel/service/ .
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libtinfo5=6.1+20181013-2+deb10u2 \
|
||||||
|
openssl=1.1.1d-0+deb10u2 \
|
||||||
|
locales=2.28-10 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||||
|
locale-gen
|
||||||
|
ENV LANG en_US.UTF-8
|
||||||
|
ENV LANGUAGE en_US:en
|
||||||
|
ENV LC_ALL en_US.UTF-8
|
||||||
|
|
||||||
|
RUN groupadd -r shortnr && useradd --no-log-init -r -g shortnr shortnr
|
||||||
|
RUN chown -R shortnr:shortnr /home/shortnr
|
||||||
|
USER shortnr
|
||||||
|
|
||||||
|
ENTRYPOINT ["bin/service", "start"]
|
||||||
|
EXPOSE 8080
|
|
@ -0,0 +1,19 @@
|
||||||
|
.PHONY: all build clean install start test
|
||||||
|
|
||||||
|
build: install test clean
|
||||||
|
docker build -t shortnr:latest .
|
||||||
|
|
||||||
|
clean:
|
||||||
|
mix clean --deps
|
||||||
|
|
||||||
|
install:
|
||||||
|
mix deps.get
|
||||||
|
|
||||||
|
install-prod:
|
||||||
|
mix deps.get --only prod
|
||||||
|
|
||||||
|
start:
|
||||||
|
iex -S mix run --no-halt
|
||||||
|
|
||||||
|
test:
|
||||||
|
mix test
|
|
@ -0,0 +1,8 @@
|
||||||
|
import Config
|
||||||
|
|
||||||
|
config :service,
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
config :logger, :console,
|
||||||
|
format: "date=$date time=$time level=$level$levelpad message=\"$message\" $metadata\n",
|
||||||
|
metadata: [:port, :file, :line, :crash_reason, :stack]
|
|
@ -0,0 +1,35 @@
|
||||||
|
defmodule Shortnr.Router do
|
||||||
|
use Plug.ErrorHandler
|
||||||
|
use Plug.Router
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
alias Shortnr.Transport.{Json, Http}
|
||||||
|
alias Shortnr.Url
|
||||||
|
|
||||||
|
plug(Plug.Logger, log: :debug)
|
||||||
|
plug(:match)
|
||||||
|
plug(:dispatch)
|
||||||
|
|
||||||
|
post "/urls" do
|
||||||
|
conn
|
||||||
|
|> Json.decode_request(Url.CreateRequest)
|
||||||
|
|> Http.handle(&Url.create(&1, Url.Repo.DETS))
|
||||||
|
|> Json.encode_response()
|
||||||
|
|> Http.send(:created, conn)
|
||||||
|
end
|
||||||
|
|
||||||
|
match _ do
|
||||||
|
{:error, {:not_found, "route not found"}, conn}
|
||||||
|
|> Json.encode_response()
|
||||||
|
|> Http.send(:ignore, conn)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_errors(conn, %{kind: _kind, reason: reason, stack: stack}) do
|
||||||
|
Logger.error(inspect(reason), stack: stack)
|
||||||
|
|
||||||
|
{:error, {:internal_server_error, "internal server error"}, conn}
|
||||||
|
|> Json.encode_response()
|
||||||
|
|> Http.send(:ignore, conn)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule Shortnr do
|
||||||
|
@moduledoc """
|
||||||
|
Documentation for Shortnr.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Application
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def start(_type, _args) do
|
||||||
|
port = port()
|
||||||
|
|
||||||
|
children = [
|
||||||
|
{Plug.Cowboy, scheme: :http, plug: Shortnr.Router, options: [port: port]}
|
||||||
|
]
|
||||||
|
|
||||||
|
Logger.info("server starting", port: port)
|
||||||
|
Supervisor.start_link(children, strategy: :one_for_one)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec port() :: integer()
|
||||||
|
defp port do
|
||||||
|
case Application.fetch_env(:service, :port) do
|
||||||
|
{:ok, port} -> port
|
||||||
|
_ -> 4000
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,36 @@
|
||||||
|
defmodule Shortnr.Transport.Http do
|
||||||
|
import Plug.Conn
|
||||||
|
|
||||||
|
@type ok_error :: {:ok, term(), Plug.Conn.t()} | error()
|
||||||
|
@type error :: {:error, {atom(), String.t()}, Plug.Conn.t()}
|
||||||
|
|
||||||
|
@spec handle(ok_error(), (term() -> ok_error())) :: ok_error()
|
||||||
|
def handle(error = {:error, _sub_error, _conn}, _func), do: error
|
||||||
|
|
||||||
|
def handle({:ok, request, conn}, func) do
|
||||||
|
case func.(request) do
|
||||||
|
{:ok, response} -> {:ok, response, conn}
|
||||||
|
{:error, error_value} -> convert_error(error_value, conn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp convert_error({:invalid_argument, message}, conn),
|
||||||
|
do: {:error, {:bad_request, message}, conn}
|
||||||
|
|
||||||
|
defp convert_error(_, conn), do: {:error, {:internal_server_error, "unknown error"}, conn}
|
||||||
|
|
||||||
|
@spec send(ok_error(), atom(), Plug.Conn.t()) :: Plug.Conn.t()
|
||||||
|
def send({:error, {status, body}, _conn}, _success_status, original_conn) do
|
||||||
|
send_resp(original_conn, status_to_code(status), body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def send({:ok, body, conn}, success_status, _original_conn) do
|
||||||
|
send_resp(conn, status_to_code(success_status), body)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp status_to_code(:ok), do: 200
|
||||||
|
defp status_to_code(:created), do: 201
|
||||||
|
defp status_to_code(:bad_request), do: 400
|
||||||
|
defp status_to_code(:not_found), do: 404
|
||||||
|
defp status_to_code(:internal_server_error), do: 500
|
||||||
|
end
|
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule Shortnr.Transport.Json do
|
||||||
|
import Plug.Conn
|
||||||
|
alias Shortnr.Transport.Http
|
||||||
|
|
||||||
|
@spec decode_request(Plug.Conn.t(), module()) :: Http.ok_error()
|
||||||
|
def decode_request(conn, struct_module) do
|
||||||
|
{:ok, body, conn} = read_body(conn)
|
||||||
|
{:ok, params} = Jason.decode(body)
|
||||||
|
|
||||||
|
params_list =
|
||||||
|
params
|
||||||
|
|> Map.to_list()
|
||||||
|
|> Enum.map(fn {key, value} -> {String.to_atom(key), value} end)
|
||||||
|
|
||||||
|
{:ok, struct(struct_module, params_list), conn}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec encode_response(Http.ok_error()) :: Http.ok_error()
|
||||||
|
def encode_response({:error, {status, response}, conn}) do
|
||||||
|
{:ok, json_body} = Jason.encode(%{data: response})
|
||||||
|
{:error, {status, json_body}, conn}
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_response({:ok, response, conn}) do
|
||||||
|
{:ok, json_body} = Jason.encode(%{data: response})
|
||||||
|
{:ok, json_body, conn}
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,4 @@
|
||||||
|
defmodule Shortnr.Transport do
|
||||||
|
@type error :: {:error, {atom(), String.t()}}
|
||||||
|
@type ok_error :: {:ok, term()} | error()
|
||||||
|
end
|
|
@ -0,0 +1,32 @@
|
||||||
|
defmodule Shortnr.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :service,
|
||||||
|
version: "0.1.0",
|
||||||
|
elixir: "~> 1.9",
|
||||||
|
start_permanent: Mix.env() == :prod,
|
||||||
|
deps: deps()
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run "mix help compile.app" to learn about applications.
|
||||||
|
def application do
|
||||||
|
[
|
||||||
|
mod: {Shortnr, []},
|
||||||
|
extra_applications: [:logger]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run "mix help deps" to learn about dependencies.
|
||||||
|
defp deps do
|
||||||
|
[
|
||||||
|
{:plug_cowboy, "~> 2.0"},
|
||||||
|
{:elixir_uuid, "~> 1.2"},
|
||||||
|
{:jason, "~> 1.1"}
|
||||||
|
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||||
|
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,11 @@
|
||||||
|
%{
|
||||||
|
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm"},
|
||||||
|
"elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm"},
|
||||||
|
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
|
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
||||||
|
"plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
|
"plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
|
||||||
|
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
ExUnit.start()
|
Loading…
Reference in New Issue