summaryrefslogtreecommitdiff
path: root/lib/silmataivas_web
diff options
context:
space:
mode:
Diffstat (limited to 'lib/silmataivas_web')
-rw-r--r--lib/silmataivas_web/controllers/changeset_json.ex25
-rw-r--r--lib/silmataivas_web/controllers/error_json.ex21
-rw-r--r--lib/silmataivas_web/controllers/fallback_controller.ex24
-rw-r--r--lib/silmataivas_web/controllers/health_controller.ex9
-rw-r--r--lib/silmataivas_web/controllers/location_controller.ex46
-rw-r--r--lib/silmataivas_web/controllers/location_json.ex25
-rw-r--r--lib/silmataivas_web/endpoint.ex51
-rw-r--r--lib/silmataivas_web/gettext.ex25
-rw-r--r--lib/silmataivas_web/plugs/admin_only.ex8
-rw-r--r--lib/silmataivas_web/plugs/auth.ex20
-rw-r--r--lib/silmataivas_web/router.ex41
-rw-r--r--lib/silmataivas_web/telemetry.ex93
12 files changed, 388 insertions, 0 deletions
diff --git a/lib/silmataivas_web/controllers/changeset_json.ex b/lib/silmataivas_web/controllers/changeset_json.ex
new file mode 100644
index 0000000..ac0226d
--- /dev/null
+++ b/lib/silmataivas_web/controllers/changeset_json.ex
@@ -0,0 +1,25 @@
+defmodule SilmataivasWeb.ChangesetJSON do
+ @doc """
+ Renders changeset errors.
+ """
+ def error(%{changeset: changeset}) do
+ # When encoded, the changeset returns its errors
+ # as a JSON object. So we just pass it forward.
+ %{errors: Ecto.Changeset.traverse_errors(changeset, &translate_error/1)}
+ end
+
+ defp translate_error({msg, opts}) do
+ # You can make use of gettext to translate error messages by
+ # uncommenting and adjusting the following code:
+
+ # if count = opts[:count] do
+ # Gettext.dngettext(SilmataivasWeb.Gettext, "errors", msg, msg, count, opts)
+ # else
+ # Gettext.dgettext(SilmataivasWeb.Gettext, "errors", msg, opts)
+ # end
+
+ Enum.reduce(opts, msg, fn {key, value}, acc ->
+ String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end)
+ end)
+ end
+end
diff --git a/lib/silmataivas_web/controllers/error_json.ex b/lib/silmataivas_web/controllers/error_json.ex
new file mode 100644
index 0000000..a2ca902
--- /dev/null
+++ b/lib/silmataivas_web/controllers/error_json.ex
@@ -0,0 +1,21 @@
+defmodule SilmataivasWeb.ErrorJSON do
+ @moduledoc """
+ This module is invoked by your endpoint in case of errors on JSON requests.
+
+ See config/config.exs.
+ """
+
+ # If you want to customize a particular status code,
+ # you may add your own clauses, such as:
+ #
+ # def render("500.json", _assigns) do
+ # %{errors: %{detail: "Internal Server Error"}}
+ # end
+
+ # By default, Phoenix returns the status message from
+ # the template name. For example, "404.json" becomes
+ # "Not Found".
+ def render(template, _assigns) do
+ %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
+ end
+end
diff --git a/lib/silmataivas_web/controllers/fallback_controller.ex b/lib/silmataivas_web/controllers/fallback_controller.ex
new file mode 100644
index 0000000..f315110
--- /dev/null
+++ b/lib/silmataivas_web/controllers/fallback_controller.ex
@@ -0,0 +1,24 @@
+defmodule SilmataivasWeb.FallbackController do
+ @moduledoc """
+ Translates controller action results into valid `Plug.Conn` responses.
+
+ See `Phoenix.Controller.action_fallback/1` for more details.
+ """
+ use SilmataivasWeb, :controller
+
+ # This clause handles errors returned by Ecto's insert/update/delete.
+ def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
+ conn
+ |> put_status(:unprocessable_entity)
+ |> put_view(json: SilmataivasWeb.ChangesetJSON)
+ |> render(:error, changeset: changeset)
+ end
+
+ # This clause is an example of how to handle resources that cannot be found.
+ def call(conn, {:error, :not_found}) do
+ conn
+ |> put_status(:not_found)
+ |> put_view(html: SilmataivasWeb.ErrorHTML, json: SilmataivasWeb.ErrorJSON)
+ |> render(:"404")
+ end
+end
diff --git a/lib/silmataivas_web/controllers/health_controller.ex b/lib/silmataivas_web/controllers/health_controller.ex
new file mode 100644
index 0000000..959b84b
--- /dev/null
+++ b/lib/silmataivas_web/controllers/health_controller.ex
@@ -0,0 +1,9 @@
+defmodule SilmataivasWeb.HealthController do
+ use SilmataivasWeb, :controller
+
+ def index(conn, _params) do
+ conn
+ |> put_status(:ok)
+ |> json(%{status: "ok"})
+ end
+end
diff --git a/lib/silmataivas_web/controllers/location_controller.ex b/lib/silmataivas_web/controllers/location_controller.ex
new file mode 100644
index 0000000..d494d59
--- /dev/null
+++ b/lib/silmataivas_web/controllers/location_controller.ex
@@ -0,0 +1,46 @@
+defmodule SilmataivasWeb.LocationController do
+ use SilmataivasWeb, :controller
+
+ alias Silmataivas.Locations
+ alias Silmataivas.Locations.Location
+
+ action_fallback SilmataivasWeb.FallbackController
+
+ def index(conn, _params) do
+ locations = Locations.list_locations()
+ render(conn, :index, locations: locations)
+ end
+
+ def create(conn, params) do
+ user = conn.assigns.current_user
+ params = Map.put(params, "user_id", user.id)
+
+ with {:ok, %Location{} = location} <- Locations.create_location(params) do
+ conn
+ |> put_status(:created)
+ |> put_resp_header("location", ~p"/api/locations/#{location}")
+ |> render(:show, location: location)
+ end
+ end
+
+ def show(conn, %{"id" => id}) do
+ location = Locations.get_location!(id)
+ render(conn, :show, location: location)
+ end
+
+ def update(conn, %{"id" => id, "location" => location_params}) do
+ location = Locations.get_location!(id)
+
+ with {:ok, %Location{} = location} <- Locations.update_location(location, location_params) do
+ render(conn, :show, location: location)
+ end
+ end
+
+ def delete(conn, %{"id" => id}) do
+ location = Locations.get_location!(id)
+
+ with {:ok, %Location{}} <- Locations.delete_location(location) do
+ send_resp(conn, :no_content, "")
+ end
+ end
+end
diff --git a/lib/silmataivas_web/controllers/location_json.ex b/lib/silmataivas_web/controllers/location_json.ex
new file mode 100644
index 0000000..db7e469
--- /dev/null
+++ b/lib/silmataivas_web/controllers/location_json.ex
@@ -0,0 +1,25 @@
+defmodule SilmataivasWeb.LocationJSON do
+ alias Silmataivas.Locations.Location
+
+ @doc """
+ Renders a list of locations.
+ """
+ def index(%{locations: locations}) do
+ %{data: for(location <- locations, do: data(location))}
+ end
+
+ @doc """
+ Renders a single location.
+ """
+ def show(%{location: location}) do
+ %{data: data(location)}
+ end
+
+ defp data(%Location{} = location) do
+ %{
+ id: location.id,
+ latitude: location.latitude,
+ longitude: location.longitude
+ }
+ end
+end
diff --git a/lib/silmataivas_web/endpoint.ex b/lib/silmataivas_web/endpoint.ex
new file mode 100644
index 0000000..086b1f9
--- /dev/null
+++ b/lib/silmataivas_web/endpoint.ex
@@ -0,0 +1,51 @@
+defmodule SilmataivasWeb.Endpoint do
+ use Phoenix.Endpoint, otp_app: :silmataivas
+
+ # 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.
+ @session_options [
+ store: :cookie,
+ key: "_silmataivas_key",
+ signing_salt: "Fvhz8Cqb",
+ same_site: "Lax"
+ ]
+
+ socket "/live", Phoenix.LiveView.Socket,
+ websocket: [connect_info: [session: @session_options]],
+ longpoll: [connect_info: [session: @session_options]]
+
+ # Serve at "/" the static files from "priv/static" directory.
+ #
+ # You should set gzip to true if you are running phx.digest
+ # when deploying your static files in production.
+ plug Plug.Static,
+ at: "/",
+ from: :silmataivas,
+ gzip: false,
+ only: SilmataivasWeb.static_paths()
+
+ # Code reloading can be explicitly enabled under the
+ # :code_reloader configuration of your endpoint.
+ if code_reloading? do
+ plug Phoenix.CodeReloader
+ plug Phoenix.Ecto.CheckRepoStatus, otp_app: :silmataivas
+ end
+
+ plug Phoenix.LiveDashboard.RequestLogger,
+ param_key: "request_logger",
+ cookie_key: "request_logger"
+
+ plug Plug.RequestId
+ plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
+
+ plug Plug.Parsers,
+ parsers: [:urlencoded, :multipart, :json],
+ pass: ["*/*"],
+ json_decoder: Phoenix.json_library()
+
+ plug Plug.MethodOverride
+ plug Plug.Head
+ plug Plug.Session, @session_options
+ plug SilmataivasWeb.Router
+end
diff --git a/lib/silmataivas_web/gettext.ex b/lib/silmataivas_web/gettext.ex
new file mode 100644
index 0000000..a494c80
--- /dev/null
+++ b/lib/silmataivas_web/gettext.ex
@@ -0,0 +1,25 @@
+defmodule SilmataivasWeb.Gettext do
+ @moduledoc """
+ A module providing Internationalization with a gettext-based API.
+
+ By using [Gettext](https://hexdocs.pm/gettext), your module compiles translations
+ that you can use in your application. To use this Gettext backend module,
+ call `use Gettext` and pass it as an option:
+
+ use Gettext, backend: SilmataivasWeb.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.Backend, otp_app: :silmataivas
+end
diff --git a/lib/silmataivas_web/plugs/admin_only.ex b/lib/silmataivas_web/plugs/admin_only.ex
new file mode 100644
index 0000000..b3f21dc
--- /dev/null
+++ b/lib/silmataivas_web/plugs/admin_only.ex
@@ -0,0 +1,8 @@
+defmodule SilmataivasWeb.Plugs.AdminOnly do
+ import Plug.Conn
+
+ def init(opts), do: opts
+
+ def call(%{assigns: %{current_user: %{role: "admin"}}} = conn, _opts), do: conn
+ def call(conn, _opts), do: send_resp(conn, 403, "Forbidden") |> halt()
+end
diff --git a/lib/silmataivas_web/plugs/auth.ex b/lib/silmataivas_web/plugs/auth.ex
new file mode 100644
index 0000000..ff5d25b
--- /dev/null
+++ b/lib/silmataivas_web/plugs/auth.ex
@@ -0,0 +1,20 @@
+defmodule SilmataivasWeb.Plugs.Auth do
+ import Plug.Conn
+ alias Silmataivas.Users
+ alias Silmataivas.Repo
+
+ def init(opts), do: opts
+
+ def call(conn, _opts) do
+ with ["Bearer " <> user_id] <- get_req_header(conn, "authorization"),
+ %Users.User{} = user <- Users.get_user_by_user_id(user_id),
+ loaded_user <- Repo.preload(user, :location) do
+ assign(conn, :current_user, loaded_user)
+ else
+ _ ->
+ conn
+ |> send_resp(:unauthorized, "Unauthorized")
+ |> halt()
+ end
+ end
+end
diff --git a/lib/silmataivas_web/router.ex b/lib/silmataivas_web/router.ex
new file mode 100644
index 0000000..d790ef9
--- /dev/null
+++ b/lib/silmataivas_web/router.ex
@@ -0,0 +1,41 @@
+defmodule SilmataivasWeb.Router do
+ use SilmataivasWeb, :router
+
+ pipeline :api do
+ plug :accepts, ["json"]
+ plug SilmataivasWeb.Plugs.Auth
+ end
+
+ pipeline :api_public do
+ plug :accepts, ["json"]
+ end
+
+ scope "/api", SilmataivasWeb do
+ pipe_through :api
+
+ resources "/locations", LocationController, only: [:index, :create, :show, :update]
+ end
+
+ scope "/", SilmataivasWeb do
+ pipe_through :api_public
+
+ get "/health", HealthController, :index
+ end
+
+ # Enable LiveDashboard and Swoosh mailbox preview in development
+ if Application.compile_env(:silmataivas, :dev_routes) do
+ # If you want to use the LiveDashboard in production, you should put
+ # it behind authentication and allow only admins to access it.
+ # If your application does not have an admins-only section yet,
+ # you can use Plug.BasicAuth to set up some basic authentication
+ # as long as you are also using SSL (which you should anyway).
+ import Phoenix.LiveDashboard.Router
+
+ scope "/dev" do
+ pipe_through [:fetch_session, :protect_from_forgery]
+
+ live_dashboard "/dashboard", metrics: SilmataivasWeb.Telemetry
+ forward "/mailbox", Plug.Swoosh.MailboxPreview
+ end
+ end
+end
diff --git a/lib/silmataivas_web/telemetry.ex b/lib/silmataivas_web/telemetry.ex
new file mode 100644
index 0000000..f893b0e
--- /dev/null
+++ b/lib/silmataivas_web/telemetry.ex
@@ -0,0 +1,93 @@
+defmodule SilmataivasWeb.Telemetry do
+ use Supervisor
+ import Telemetry.Metrics
+
+ def start_link(arg) do
+ Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
+ end
+
+ @impl true
+ def init(_arg) do
+ children = [
+ # Telemetry poller will execute the given period measurements
+ # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
+ {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
+ # Add reporters as children of your supervision tree.
+ # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
+ ]
+
+ Supervisor.init(children, strategy: :one_for_one)
+ end
+
+ def metrics do
+ [
+ # Phoenix Metrics
+ summary("phoenix.endpoint.start.system_time",
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.endpoint.stop.duration",
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.router_dispatch.start.system_time",
+ tags: [:route],
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.router_dispatch.exception.duration",
+ tags: [:route],
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.router_dispatch.stop.duration",
+ tags: [:route],
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.socket_connected.duration",
+ unit: {:native, :millisecond}
+ ),
+ sum("phoenix.socket_drain.count"),
+ summary("phoenix.channel_joined.duration",
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.channel_handled_in.duration",
+ tags: [:event],
+ unit: {:native, :millisecond}
+ ),
+
+ # Database Metrics
+ summary("silmataivas.repo.query.total_time",
+ unit: {:native, :millisecond},
+ description: "The sum of the other measurements"
+ ),
+ summary("silmataivas.repo.query.decode_time",
+ unit: {:native, :millisecond},
+ description: "The time spent decoding the data received from the database"
+ ),
+ summary("silmataivas.repo.query.query_time",
+ unit: {:native, :millisecond},
+ description: "The time spent executing the query"
+ ),
+ summary("silmataivas.repo.query.queue_time",
+ unit: {:native, :millisecond},
+ description: "The time spent waiting for a database connection"
+ ),
+ summary("silmataivas.repo.query.idle_time",
+ unit: {:native, :millisecond},
+ description:
+ "The time the connection spent waiting before being checked out for the query"
+ ),
+
+ # VM Metrics
+ summary("vm.memory.total", unit: {:byte, :kilobyte}),
+ summary("vm.total_run_queue_lengths.total"),
+ summary("vm.total_run_queue_lengths.cpu"),
+ summary("vm.total_run_queue_lengths.io")
+ ]
+ end
+
+ defp periodic_measurements do
+ [
+ # A module, function and arguments to be invoked periodically.
+ # This function must call :telemetry.execute/3 and a metric must be added above.
+ # {SilmataivasWeb, :count_users, []}
+ ]
+ end
+end