diff --git a/.formatter.exs b/.formatter.exs index 7539791..061dc09 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,5 +1,5 @@ [ - line_length: 300, + line_length: 150, import_deps: [:phoenix], inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"] ] diff --git a/lib/ksuite_middleware/ksuite_client.ex b/lib/ksuite_middleware/ksuite_client.ex index 186dc96..db12c36 100644 --- a/lib/ksuite_middleware/ksuite_client.ex +++ b/lib/ksuite_middleware/ksuite_client.ex @@ -5,7 +5,7 @@ defmodule KsuiteMiddleware.KsuiteClient do require Logger - plug Tesla.Middleware.BaseUrl, "https://api.infomaniak.com" + plug Tesla.Middleware.BaseUrl, State.get_ksuite_api_server() plug Tesla.Middleware.Logger, debug: false plug Tesla.Middleware.PathParams plug Tesla.Middleware.Headers, [{"User-Agent", "ksuite-middleware"}] diff --git a/lib/ksuite_middleware/state.ex b/lib/ksuite_middleware/state.ex index 11b78b6..f9aea8a 100644 --- a/lib/ksuite_middleware/state.ex +++ b/lib/ksuite_middleware/state.ex @@ -18,6 +18,9 @@ defmodule KsuiteMiddleware.State do @spec get_timezone() :: Timex.TimezoneInfo.t() def get_timezone(), do: GenServer.call(State, :get_timezone) + @spec get_ksuite_api_server() :: String.t() + def get_ksuite_api_server(), do: "https://api.infomaniak.com" + # Callbacks @impl true diff --git a/lib/ksuite_middleware_web/controllers/calendar_controller.ex b/lib/ksuite_middleware_web/controllers/calendar_controller.ex index 4824444..b92a01b 100644 --- a/lib/ksuite_middleware_web/controllers/calendar_controller.ex +++ b/lib/ksuite_middleware_web/controllers/calendar_controller.ex @@ -7,6 +7,8 @@ defmodule KsuiteMiddlewareWeb.CalendarController do require Logger + action_fallback KsuiteMiddleware.FallbackController + def get_events(conn, %{"from" => from, "to" => to, "calendar_id" => calendar_id}), do: parse_params(from, to, calendar_id) @@ -14,6 +16,13 @@ defmodule KsuiteMiddlewareWeb.CalendarController do |> then(&translate_to_events/1) |> then(&send_response(conn, &1)) + def get_events(conn, _), + do: + conn + |> put_status(:bad_request) + |> put_resp_content_type("application/problem+json") + |> render(:"400", reason: "The arguments were missing.") + # Private defp read_events({:error, error, reason}), do: {:error, error, reason} @@ -28,19 +37,28 @@ defmodule KsuiteMiddlewareWeb.CalendarController do client |> CalDAVClient.Event.get_events(calendar_url, from, to) end - defp send_response(conn, {:ok, events}), do: json(conn, events) + defp send_response(conn, {:ok, events}), + do: render(conn, :events, events: events) - defp send_response(conn, {:error, error, reason}), + defp send_response(conn, {:error, :bad_request, reason}), do: conn - |> put_status(error) - |> json(%{reason: reason}) + |> put_status(:bad_request) + |> put_resp_content_type("application/problem+json") + |> render(:"400", reason: reason) + + defp send_response(conn, {:error, :not_found, reason}), + do: + conn + |> put_status(:not_found) + |> put_resp_content_type("application/problem+json") + |> render(:"404", reason: reason) defp send_response(conn, {:error, reason}), do: conn - |> put_status(500) - |> json(%{reason: reason}) + |> put_status(:internal_server_error) + |> render(:"500", reason: reason) defp parse_params(from, to, calendar_id) do Logger.info("Parsing the arguments ...") @@ -50,9 +68,10 @@ defmodule KsuiteMiddlewareWeb.CalendarController do {:ok, calendar_id} <- parse_calendar_id(calendar_id) do {:ok, from, to, calendar_id} else - :invalid_to -> {:error, :bad_request, "The argument 'to' was invalid."} - :invalid_from -> {:error, :bad_request, "The argument 'from' was invalid."} + {:invalid_to, reason} -> {:error, :bad_request, "The argument 'to' was invalid. #{reason}"} + {:invalid_from, reason} -> {:error, :bad_request, "The argument 'from' was invalid. #{reason}"} {:invalid_calendar_id, reason} -> {:error, :bad_request, reason} + _ -> {:error, :internal_server_error, "Unhandled error occured"} end end @@ -60,18 +79,20 @@ defmodule KsuiteMiddlewareWeb.CalendarController do defp parse_calendar_id(_), do: {:invalid_calendar_id, "The calendar_id was too long."} defp parse_datetime(error_atom, datetime) when is_atom(error_atom) and is_bitstring(datetime) do - case Timex.parse!(datetime, "{ISO:Extended:Z}") do - %DateTime{} = x -> {:ok, x} - _ -> error_atom + case Timex.parse(datetime, "{ISO:Extended:Z}") do + {:ok, %DateTime{} = x} -> {:ok, x} + {:error, reason} -> {error_atom, reason} end end - defp translate_to_events({:error, :unauthorized}), do: {:error, :unauthorized, "Unauthorized access to the CalDAV server, double check your credentials."} + defp translate_to_events({:error, :unauthorized}), + do: {:error, :unauthorized, "Unauthorized access to the CalDAV server, double check your credentials."} + defp translate_to_events({:error, :not_found}), do: {:error, :not_found, "The given calendar_id was not found in the given server CalDAV."} defp translate_to_events({:error, reason}), do: {:error, reason} defp translate_to_events({:error, error, reason}), do: {:error, error, reason} defp translate_to_events({:ok, []}), do: {:ok, []} - defp translate_to_events({:ok, icalendar_events}), do: translate_to_events(icalendar_events, []) + defp translate_to_events({:ok, [%CalDAVClient.Event{} | _] = icalendar_events}), do: translate_to_events(icalendar_events, []) defp translate_to_events([], acc) when is_list(acc), do: {:ok, acc} defp translate_to_events([head | tail], acc) when is_list(acc) do @@ -83,7 +104,12 @@ defmodule KsuiteMiddlewareWeb.CalendarController do from = translate_date_to_utc(from) to = translate_date_to_utc(to) - new_event = %KsuiteCalendarEvent{subject: summary, from: Timex.format!(from, "{ISO:Extended:Z}"), to: Timex.format!(to, "{ISO:Extended:Z}"), description: description} + new_event = %KsuiteCalendarEvent{ + subject: summary, + from: Timex.format!(from, "{ISO:Extended:Z}"), + to: Timex.format!(to, "{ISO:Extended:Z}"), + description: description + } translate_to_events(tail, [new_event | acc]) end diff --git a/lib/ksuite_middleware_web/controllers/calendar_json.ex b/lib/ksuite_middleware_web/controllers/calendar_json.ex new file mode 100644 index 0000000..3f27c56 --- /dev/null +++ b/lib/ksuite_middleware_web/controllers/calendar_json.ex @@ -0,0 +1,25 @@ +defmodule KsuiteMiddlewareWeb.CalendarJSON do + alias KsuiteMiddlewareWeb.Models.KsuiteCalendarEvent + + def events(%{events: [%KsuiteCalendarEvent{} | _] = events}), do: events + + def render("400.json", %{reason: reason}), + do: %{title: "invalid argument", status: 400, detail: reason} + + def render("404.json", %{reason: reason}), + do: %{ + title: "not found", + status: 404, + detail: reason, + description: + "Oh snap! It seems we've hit a hiccup in our treasure hunt—what you seek is playing hide and seek in the " <> + "digital jungle! Keep those eagle eyes peeled, and perhaps try a different map or a clever keyword dance " <> + "to coax the elusive information out of hiding." + } + + def render("500.json", %{reason: reason}), + do: %{title: "internal server error", status: 500, detail: reason} + + def render(template, _assigns), + do: %{detail: Phoenix.Controller.status_message_from_template(template)} +end diff --git a/lib/ksuite_middleware_web/controllers/error_controller.ex b/lib/ksuite_middleware_web/controllers/error_controller.ex deleted file mode 100644 index 1379a83..0000000 --- a/lib/ksuite_middleware_web/controllers/error_controller.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule KsuiteMiddlewareWeb.ErrorController do - use KsuiteMiddlewareWeb, :controller - - def not_found(conn, _params), - do: - conn - |> put_resp_header("content-type", "text/html; charset=utf-8") - |> send_file(404, Application.app_dir(:ksuite_middleware, "priv/static/not-found.html")) -end diff --git a/lib/ksuite_middleware_web/controllers/error_json.ex b/lib/ksuite_middleware_web/controllers/error_json.ex index 718bb1e..33443de 100644 --- a/lib/ksuite_middleware_web/controllers/error_json.ex +++ b/lib/ksuite_middleware_web/controllers/error_json.ex @@ -6,6 +6,17 @@ defmodule KsuiteMiddlewareWeb.ErrorJSON do # %{errors: %{detail: "Internal Server Error"}} # end + def render("404.json", _), + do: %{ + title: "not found", + status: 404, + detail: "The resource requested was not found.", + description: + "Oh snap! It seems we've hit a hiccup in our treasure hunt—what you seek is playing hide and seek in the " <> + "digital jungle! Keep those eagle eyes peeled, and perhaps try a different map or a clever keyword dance " <> + "to coax the elusive information out of hiding." + } + # By default, Phoenix returns the status message from # the template name. For example, "404.json" becomes # "Not Found". diff --git a/lib/ksuite_middleware_web/controllers/fallback_controller.ex b/lib/ksuite_middleware_web/controllers/fallback_controller.ex new file mode 100644 index 0000000..984e82f --- /dev/null +++ b/lib/ksuite_middleware_web/controllers/fallback_controller.ex @@ -0,0 +1,16 @@ +defmodule KsuiteMiddlewareWeb.FallbackController do + @moduledoc """ + Translates controller action results into valid `Plug.Conn` responses. + + See `Phoenix.Controller.action_fallback/1` for more details. + """ + use KsuiteMiddlewareWeb, :controller + + # 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(json: KsuiteMiddlewareWeb.ErrorJSON) + |> render(:"404") + end +end diff --git a/lib/ksuite_middleware_web/controllers/kdrive_controller.ex b/lib/ksuite_middleware_web/controllers/kdrive_controller.ex index 895e66e..267b598 100644 --- a/lib/ksuite_middleware_web/controllers/kdrive_controller.ex +++ b/lib/ksuite_middleware_web/controllers/kdrive_controller.ex @@ -3,11 +3,16 @@ defmodule KsuiteMiddlewareWeb.KdriveController do alias KsuiteMiddleware.KsuiteClient + action_fallback KsuiteMiddleware.FallbackController + def pass_thru(conn, %{"file_id" => id}) when is_integer(id) do with {:ok, response} <- KsuiteClient.download(id) do conn |> put_tesla_response(response) else - _ -> conn |> resp(500, "an unknown error occurred.") + _ -> + conn + |> put_status(:bad_gateway) + |> render(:"500") end end @@ -16,21 +21,41 @@ defmodule KsuiteMiddlewareWeb.KdriveController do {:ok, response} <- KsuiteClient.download(file_id) do conn |> put_tesla_response(response) else + :error -> + conn + |> put_status(:bad_request) + |> put_resp_content_type("application/problem+json") + |> render(:"400", reason: :invalid_integer) + + {:error, _} -> + conn + |> put_status(:bad_gateway) + |> put_resp_content_type("application/problem+json") + |> render(:"502", reason: :invalid_response_from_api) + _ -> - conn |> resp(500, "an unknown error occurred.") + conn + |> put_status(:internal_server_error) + |> put_resp_content_type("application/problem+json") + |> render(:"500", reason: :unknown) end end def pass_thru(conn, _params), - do: conn |> resp(400, "The file id was missing.") + do: + conn + |> put_status(:bad_request) + |> put_resp_content_type("application/problem+json") + |> render(:"400", reason: :missing_file_id) # Private defp put_tesla_response(%Plug.Conn{} = conn, %Tesla.Env{status: 401}), do: conn - |> put_resp_header("content-type", "text/html; charset=utf-8") - |> send_file(400, Application.app_dir(:ksuite_middleware, "priv/static/bad-api-key.html")) + |> put_status(:bad_request) + |> put_resp_content_type("application/problem+json") + |> render(:"400", reason: :bad_api_key) defp put_tesla_response(%Plug.Conn{} = conn, %Tesla.Env{} = response) do %Tesla.Env{status: status, body: body} = response @@ -42,7 +67,8 @@ defmodule KsuiteMiddlewareWeb.KdriveController do else _ -> conn - |> resp(status, body) + |> put_status(status) + |> render(status |> Integer.to_string() |> String.to_atom()) end end diff --git a/lib/ksuite_middleware_web/controllers/kdrive_json.ex b/lib/ksuite_middleware_web/controllers/kdrive_json.ex new file mode 100644 index 0000000..d5f53d9 --- /dev/null +++ b/lib/ksuite_middleware_web/controllers/kdrive_json.ex @@ -0,0 +1,29 @@ +defmodule KsuiteMiddlewareWeb.KdriveJSON do + alias KsuiteMiddleware.State + + def render("400.json", %{reason: :invalid_integer}), + do: %{title: "invalid argument", status: 400, detail: "The given file_id was invalid."} + + def render("400.json", %{reason: :missing_file_id}), + do: %{title: "missing argument", status: 400, detail: "The file_id was missing."} + + def render("400.json", %{reason: :bad_api_key}), + do: %{ + title: "bad api key", + status: 400, + detail: "The API key is invalid, please verify the 'KSUITE_API_TOKEN'.", + description: + "Whoopsie-daisy! Looks like we got a little lost in API land. Our secret handshake seems a bit off. " <> + "Double-check that magic spell in the enchanted scroll labeled `KSUITE_API_TOKEN` and make sure it's been " <> + "properly whispered into the winds of the environment variable realm." + } + + def render("502.json", %{reason: :invalid_response_from_api}), + do: %{title: "bad gateway", status: 502, detail: "The upstream api returned an invalid response.", server: State.get_ksuite_api_server()} + + def render("500.json", %{reason: :unknown}), + do: %{title: "internal server error", status: 500, detail: "An unhandled error was returned, please contact the administrator."} + + def render(template, _assigns), + do: %{detail: Phoenix.Controller.status_message_from_template(template)} +end diff --git a/lib/ksuite_middleware_web/router.ex b/lib/ksuite_middleware_web/router.ex index 7af385b..6d60349 100644 --- a/lib/ksuite_middleware_web/router.ex +++ b/lib/ksuite_middleware_web/router.ex @@ -43,10 +43,4 @@ defmodule KsuiteMiddlewareWeb.Router do live_dashboard "/dashboard", metrics: KsuiteMiddlewareWeb.Telemetry end end - - scope "/", KsuiteMiddlewareWeb do - pipe_through :browser - - get "/*path", ErrorController, :not_found - end end diff --git a/priv/static/bad-api-key.html b/priv/static/bad-api-key.html deleted file mode 100644 index 4d3a76a..0000000 --- a/priv/static/bad-api-key.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - Ksuite Middleware - 400 - - - - -
-

Ksuite Middleware - 400

-
-

- Whoopsie-daisy! Looks like we got a little lost in API land. Our secret handshake seems a bit off. - Double-check that magic spell in the enchanted scroll labeled `KSUITE_API_TOKEN` and make sure it's been - properly whispered into the winds of the environment variable realm. -

-
-
- - - \ No newline at end of file diff --git a/priv/static/not-found.html b/priv/static/not-found.html deleted file mode 100644 index a6c43a4..0000000 --- a/priv/static/not-found.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - Ksuite Middleware - 404 - - - - -
-

Ksuite Middleware - 404

-
-

- Oh snap! It seems we've hit a hiccup in our treasure hunt—what you seek is playing hide and seek in the - digital jungle! Keep those eagle eyes peeled, and perhaps try a different map or a clever keyword dance - to coax the elusive information out of hiding. -

-
-
- - - \ No newline at end of file