diff options
Diffstat (limited to 'lib/silmataivas_web')
| -rw-r--r-- | lib/silmataivas_web/controllers/changeset_json.ex | 25 | ||||
| -rw-r--r-- | lib/silmataivas_web/controllers/error_json.ex | 21 | ||||
| -rw-r--r-- | lib/silmataivas_web/controllers/fallback_controller.ex | 24 | ||||
| -rw-r--r-- | lib/silmataivas_web/controllers/health_controller.ex | 9 | ||||
| -rw-r--r-- | lib/silmataivas_web/controllers/location_controller.ex | 46 | ||||
| -rw-r--r-- | lib/silmataivas_web/controllers/location_json.ex | 25 | ||||
| -rw-r--r-- | lib/silmataivas_web/endpoint.ex | 51 | ||||
| -rw-r--r-- | lib/silmataivas_web/gettext.ex | 25 | ||||
| -rw-r--r-- | lib/silmataivas_web/plugs/admin_only.ex | 8 | ||||
| -rw-r--r-- | lib/silmataivas_web/plugs/auth.ex | 20 | ||||
| -rw-r--r-- | lib/silmataivas_web/router.ex | 41 | ||||
| -rw-r--r-- | lib/silmataivas_web/telemetry.ex | 93 |
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 |
