diff --git a/.vscode/thunder-tests/thunderCollection.json b/.vscode/thunder-tests/thunderCollection.json index 5850e6e3..f12068f0 100644 --- a/.vscode/thunder-tests/thunderCollection.json +++ b/.vscode/thunder-tests/thunderCollection.json @@ -75,6 +75,13 @@ "headers": [], "tests": [] } + }, + { + "_id": "218855f8-9f59-41fb-8da4-163a31e28110", + "name": "devices", + "containerId": "a94213ca-8f76-4321-98c8-c7963d59efb9", + "created": "2023-01-03T15:17:08.973Z", + "sortNum": 110000 } ], "settings": { diff --git a/.vscode/thunder-tests/thunderEnvironment.json b/.vscode/thunder-tests/thunderEnvironment.json index b668a3f5..b7a3e386 100644 --- a/.vscode/thunder-tests/thunderEnvironment.json +++ b/.vscode/thunder-tests/thunderEnvironment.json @@ -54,6 +54,10 @@ { "name": "app_name", "value": "Lenra Test 3" + }, + { + "name": "device_id", + "value": "randomdeviceid42" } ] } diff --git a/.vscode/thunder-tests/thunderclient.json b/.vscode/thunder-tests/thunderclient.json index 24cec52b..db2870bc 100644 --- a/.vscode/thunder-tests/thunderclient.json +++ b/.vscode/thunder-tests/thunderclient.json @@ -425,5 +425,24 @@ "headers": [], "params": [], "tests": [] + }, + { + "_id": "adb60f75-7c6c-4417-a5c6-c6f4c442ef41", + "colId": "786cd85e-2a59-44b6-a835-45cd23e28b73", + "containerId": "218855f8-9f59-41fb-8da4-163a31e28110", + "name": "notify_provider", + "url": "{{endpoint}}/api/devices/{{device_id}}/notify_provider", + "method": "PUT", + "sortNum": 310000, + "created": "2023-01-03T15:17:29.268Z", + "modified": "2023-01-03T15:20:36.492Z", + "headers": [], + "params": [], + "body": { + "type": "json", + "raw": "{\n \"endpoint\": \"http://localhost:4000/up/upDgrbsMX3yh7K?up=1\",\n \"system\": \"unified_push\"\n}", + "form": [] + }, + "tests": [] } ] \ No newline at end of file diff --git a/apps/lenra/lib/lenra/accounts/user.ex b/apps/lenra/lib/lenra/accounts/user.ex index 79e353b7..4cbfb002 100644 --- a/apps/lenra/lib/lenra/accounts/user.ex +++ b/apps/lenra/lib/lenra/accounts/user.ex @@ -23,6 +23,8 @@ defmodule Lenra.Accounts.User do alias Lenra.Legal.{CGU, UserAcceptCGUVersion} + alias Lenra.Notifications.NotifyProvider + @type t :: %__MODULE__{} @email_regex ~r/[^@]+@[^\.]+\..+/ @@ -46,6 +48,7 @@ defmodule Lenra.Accounts.User do has_many(:builds, Build, foreign_key: :creator_id) has_many(:environments, Environment, foreign_key: :creator_id) has_many(:deployments, Deployment, foreign_key: :publisher_id) + has_many(:notif_providers, NotifyProvider) many_to_many(:environments_accesses, Environment, join_through: UserEnvironmentAccess) many_to_many(:cgus, CGU, join_through: UserAcceptCGUVersion) timestamps() diff --git a/apps/lenra/lib/lenra/application.ex b/apps/lenra/lib/lenra/application.ex index ceaa12b5..ccfd700f 100644 --- a/apps/lenra/lib/lenra/application.ex +++ b/apps/lenra/lib/lenra/application.ex @@ -19,22 +19,27 @@ defmodule Lenra.Application do # Start the Event Queue {EventQueue, &Lenra.LoadWorker.load/0}, # Start the HTTP Client - Supervisor.child_spec( - {Finch, - name: FaasHttp, - pools: %{ - Application.fetch_env!(:lenra, :faas_url) => [size: 32, count: 8] - }}, - id: :finch_faas_http - ), - Supervisor.child_spec( - {Finch, - name: GitlabHttp, - pools: %{ - Application.fetch_env!(:lenra, :gitlab_api_url) => [size: 10, count: 3] - }}, - id: :finch_gitlab_http - ), + { + Finch, + name: FaasHttp, + pools: %{ + Application.fetch_env!(:lenra, :faas_url) => [size: 32, count: 8] + } + }, + { + Finch, + name: GitlabHttp, + pools: %{ + Application.fetch_env!(:lenra, :gitlab_api_url) => [size: 10, count: 3] + } + }, + { + Finch, + name: UnifiedPushHttp, + pools: %{ + "http://localhost:8001" => [size: 10, count: 3] + } + }, {Cluster.Supervisor, [Application.get_env(:libcluster, :topologies), [name: Lenra.ClusterSupervisor]]} ] diff --git a/apps/lenra/lib/lenra/notifications.ex b/apps/lenra/lib/lenra/notifications.ex new file mode 100644 index 00000000..7007e1e4 --- /dev/null +++ b/apps/lenra/lib/lenra/notifications.ex @@ -0,0 +1,49 @@ +defmodule Lenra.Notifications do + alias Lenra.NotifyWorker + alias Lenra.Repo + alias Lenra.Notifications.{NotifyProvider} + alias ApplicationRunner.Notifications.Notif + import Ecto.Query, only: [from: 2] + + def set_notify_provider(params) do + params + |> NotifyProvider.new() + |> Repo.insert( + on_conflict: :replace_all, + conflict_target: [:device_id] + ) + end + + defp get_providers(user_ids) do + from(np in NotifyProvider, where: np.user_id in ^user_ids) + |> Repo.all() + end + + def notify(%Notif{} = notif) do + get_providers(notif.to_uids) + |> Enum.map(fn %NotifyProvider{} = provider -> + NotifyWorker.add_push_notif(provider, notif) + end) + end + + def send_up_notification(%NotifyProvider{} = provider, %Notif{} = notif) do + string_params_body = construct_string_body(notif) + + Finch.build(:post, provider.endpoint, [], string_params_body) + |> Finch.request(UnifiedPushHttp) + end + + defp construct_string_body(%Notif{} = body) do + [:message, :title, :tags, :priority, :attach, :actions, :click, :at] + |> Enum.reduce("", fn elem, url_params -> + case Map.get(body, elem) do + nil -> + url_params + + res -> + key = Atom.to_string(elem) + "#{url_params}&#{key}=#{res}" + end + end) + end +end diff --git a/apps/lenra/lib/lenra/notifications/notify_provider.ex b/apps/lenra/lib/lenra/notifications/notify_provider.ex new file mode 100644 index 00000000..e14d0d8f --- /dev/null +++ b/apps/lenra/lib/lenra/notifications/notify_provider.ex @@ -0,0 +1,36 @@ +defmodule Lenra.Notifications.NotifyProvider do + use Lenra.Schema + import Ecto.Changeset + + alias Lenra.Notifications.NotifyProvider + alias Lenra.Accounts.User + + @derive {Jason.Encoder, + only: [ + :device_id, + :endpoint, + :system, + :user_id + ]} + + schema "notify_provider" do + field(:device_id, :string) + field(:endpoint, :string) + field(:system, Ecto.Enum, values: [:unified_push, :fcm, :apns, :ws]) + belongs_to(:user, User) + + timestamps() + end + + def changeset(dev_code, params \\ %{}) do + dev_code + |> cast(params, [:device_id, :user_id, :endpoint, :system]) + |> validate_required([:device_id, :user_id, :endpoint, :system]) + |> unique_constraint([:device_id]) + |> foreign_key_constraint(:user_id) + end + + def new(params) do + changeset(%NotifyProvider{}, params) + end +end diff --git a/apps/lenra/lib/lenra/workers/load_workers.ex b/apps/lenra/lib/lenra/workers/load_workers.ex index 90c40077..67688da7 100644 --- a/apps/lenra/lib/lenra/workers/load_workers.ex +++ b/apps/lenra/lib/lenra/workers/load_workers.ex @@ -2,11 +2,12 @@ defmodule Lenra.LoadWorker do @moduledoc """ function to load worker in eventQueue """ - alias Lenra.EmailWorker + alias Lenra.{EmailWorker, NotifyWorker} def load do EventQueue.add_worker(EmailWorker, :email_verification) EventQueue.add_worker(EmailWorker, :email_password_lost) EventQueue.add_worker(EmailWorker, :email_invitation) + EventQueue.add_worker(NotifyWorker, :unified_push) end end diff --git a/apps/lenra/lib/lenra/workers/notify_worker.ex b/apps/lenra/lib/lenra/workers/notify_worker.ex new file mode 100644 index 00000000..2ecdd775 --- /dev/null +++ b/apps/lenra/lib/lenra/workers/notify_worker.ex @@ -0,0 +1,21 @@ +defmodule Lenra.NotifyWorker do + @moduledoc """ + Worker to use for send email + """ + alias Lenra.Notifications + require Logger + + def add_push_notif(provider, notif) do + EventQueue.add_event(:push_notif, [provider, notif]) + end + + def push_notif(provider, notif) do + case provider.system do + :unified_push -> + Notifications.send_up_notification(provider, notif) + + _ -> + raise "Not implemented" + end + end +end diff --git a/apps/lenra/mix.exs b/apps/lenra/mix.exs index 43629a58..85272c66 100644 --- a/apps/lenra/mix.exs +++ b/apps/lenra/mix.exs @@ -34,13 +34,13 @@ defmodule Lenra.MixProject do [ {:phoenix_pubsub, "~> 2.0"}, {:telemetry, "~> 0.4.3", override: true}, - {:ecto_sql, "~> 3.4"}, + {:ecto_sql, "~> 3.9.2"}, {:bamboo, "~> 2.2.0"}, {:bamboo_smtp, "~> 4.2.2"}, - {:postgrex, "~> 0.15.8"}, + {:postgrex, "~> 0.16.0"}, {:jason, "~> 1.2"}, {:json_diff, "~> 0.1.0"}, - {:guardian, "~> 2.1.1"}, + {:guardian, "~> 2.3.1"}, {:guardian_db, "~> 2.0"}, {:argon2_elixir, "~> 2.0"}, {:sentry, "~> 8.0"}, @@ -48,12 +48,14 @@ defmodule Lenra.MixProject do {:event_queue, git: "https://github.com/lenra-io/event-queue.git", tag: "v1.0.0"}, {:earmark, "~> 1.4.20", only: [:dev, :test], runtime: false}, {:libcluster, "~> 3.3"}, - {:application_runner, - git: "https://github.com/lenra-io/application-runner.git", - ref: "v1.0.0-beta.102", - submodules: true}, - {:lenra_common, git: "https://github.com/lenra-io/lenra-common.git", tag: "v2.4.0"} - + { + :application_runner, + # git: "https://github.com/lenra-io/application-runner.git", + # tag: "v1.0.0-beta.102", + # submodules: true + path: "../../../application-runner" + }, + {:lenra_common, git: "https://github.com/lenra-io/lenra-common.git", tag: "v2.5.0"} ] end end diff --git a/apps/lenra/priv/repo/migrations/20230102135126_add_notification_table.exs b/apps/lenra/priv/repo/migrations/20230102135126_add_notification_table.exs new file mode 100644 index 00000000..33ab1514 --- /dev/null +++ b/apps/lenra/priv/repo/migrations/20230102135126_add_notification_table.exs @@ -0,0 +1,15 @@ +defmodule Lenra.Repo.Migrations.AddNotificationTable do + use Ecto.Migration + + def change do + create table(:notify_provider) do + add(:user_id, references(:users), null: false) + add(:device_id, :string, null: false) + add(:endpoint, :string, null: false) + add(:system, :string, null: false) + timestamps() + end + + create(unique_index(:notify_provider, [:device_id])) + end +end diff --git a/apps/lenra_web/lib/lenra_web/app_adapter.ex b/apps/lenra_web/lib/lenra_web/app_adapter.ex index 9dbb59a3..c10a6e95 100644 --- a/apps/lenra_web/lib/lenra_web/app_adapter.ex +++ b/apps/lenra_web/lib/lenra_web/app_adapter.ex @@ -9,6 +9,7 @@ defmodule LenraWeb.AppAdapter do alias Lenra.{Apps, Repo} alias Lenra.Apps.{App, Environment, MainEnv} alias Lenra.Errors.BusinessError + alias ApplicationRunner.Notifications.Notif @impl ApplicationRunner.Adapter def allow(user_id, app_name) do @@ -69,6 +70,13 @@ defmodule LenraWeb.AppAdapter do end end + @impl ApplicationRunner.Adapter + def send_notification(%Notif{} = notif) do + Lenra.Notifications.notify(notif) + + :ok + end + defmodule Policy do @moduledoc """ This policy defines the rules to join an application. diff --git a/apps/lenra_web/lib/lenra_web/controllers/notification_controller.ex b/apps/lenra_web/lib/lenra_web/controllers/notification_controller.ex new file mode 100644 index 00000000..04814bb7 --- /dev/null +++ b/apps/lenra_web/lib/lenra_web/controllers/notification_controller.ex @@ -0,0 +1,29 @@ +defmodule LenraWeb.NotificationController do + use ApplicationRunner.NotificationsController, adapter: LenraWeb.AppAdapter + + use LenraWeb.Policy, + module: LenraWeb.NotificationController.Policy + + import Plug.Conn + + require Logger + + alias Lenra.Notifications + alias LenraWeb.Guardian.Plug + + def put_provider(conn, params) do + with :ok <- allow(conn), + user <- Plug.current_resource(conn), + {:ok, provider} <- params |> Map.put("user_id", user.id) |> Notifications.set_notify_provider() do + reply(conn, provider) + end + end +end + +defmodule LenraWeb.NotificationController.Policy do + @impl Bouncer.Policy + def authorize(:put_provider, _user, _data), do: true + + # credo:disable-for-next-line Credo.Check.Readability.StrictModuleLayout + use LenraWeb.Policy.Default +end diff --git a/apps/lenra_web/lib/lenra_web/controllers/user_controller.ex b/apps/lenra_web/lib/lenra_web/controllers/user_controller.ex index 34b28b4b..d1cf47dc 100644 --- a/apps/lenra_web/lib/lenra_web/controllers/user_controller.ex +++ b/apps/lenra_web/lib/lenra_web/controllers/user_controller.ex @@ -9,7 +9,11 @@ defmodule LenraWeb.UserController do alias Lenra.Errors.BusinessError + require Logger + def register(conn, params) do + Logger.debug(inspect(params)) + with {:ok, %{inserted_user: user}} <- Accounts.register_user(params) do conn |> TokenHelper.assign_access_and_refresh_token(user) diff --git a/apps/lenra_web/lib/lenra_web/router.ex b/apps/lenra_web/lib/lenra_web/router.ex index dbad0daa..55688a93 100644 --- a/apps/lenra_web/lib/lenra_web/router.ex +++ b/apps/lenra_web/lib/lenra_web/router.ex @@ -5,7 +5,7 @@ defmodule LenraWeb.Router do require ApplicationRunner.Router - ApplicationRunner.Router.app_routes() + ApplicationRunner.Router.app_routes(notification_controller: LenraWeb.NotificationController) pipeline :api do plug(:accepts, ["json"]) @@ -82,6 +82,8 @@ defmodule LenraWeb.Router do get("/webhooks", WebhooksController, :index) post("/webhooks", WebhooksController, :api_create) + + put("/devices/:device_id/notify_provider", NotificationController, :put_provider) end scope "/api", LenraWeb do diff --git a/apps/lenra_web/mix.exs b/apps/lenra_web/mix.exs index d8ba5337..bd5d5894 100644 --- a/apps/lenra_web/mix.exs +++ b/apps/lenra_web/mix.exs @@ -33,7 +33,7 @@ defmodule LenraWeb.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:phoenix, "~> 1.5.9"}, + {:phoenix, "~> 1.6.15"}, {:telemetry, "~> 0.4.3", override: true}, {:phoenix_live_dashboard, "~> 0.4"}, {:telemetry_metrics, "~> 0.4"}, @@ -42,9 +42,10 @@ defmodule LenraWeb.MixProject do {:phoenix_ecto, "~> 4.1"}, {:sentry, "~> 8.0"}, {:lenra, in_umbrella: true}, + # {:ntfy_proxy, in_umbrella: true}, {:cors_plug, "~> 3.0", only: :dev, runtime: false}, {:bouncer, git: "https://github.com/lenra-io/bouncer.git", tag: "v1.0.0"}, - {:lenra_common, git: "https://github.com/lenra-io/lenra-common.git", tag: "v2.4.0"} + {:lenra_common, git: "https://github.com/lenra-io/lenra-common.git", tag: "v2.5.0"}, ] end end diff --git a/apps/ntfy_proxy/.formatter.exs b/apps/ntfy_proxy/.formatter.exs new file mode 100644 index 00000000..47616780 --- /dev/null +++ b/apps/ntfy_proxy/.formatter.exs @@ -0,0 +1,4 @@ +[ + import_deps: [:phoenix], + inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/apps/ntfy_proxy/.gitignore b/apps/ntfy_proxy/.gitignore new file mode 100644 index 00000000..52289951 --- /dev/null +++ b/apps/ntfy_proxy/.gitignore @@ -0,0 +1,24 @@ +# 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 3rd-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"). +ntfy_proxy-*.tar + diff --git a/apps/ntfy_proxy/README.md b/apps/ntfy_proxy/README.md new file mode 100644 index 00000000..a51d7e51 --- /dev/null +++ b/apps/ntfy_proxy/README.md @@ -0,0 +1,18 @@ +# NtfyProxy + +To start your Phoenix server: + + * Install dependencies with `mix deps.get` + * Start Phoenix endpoint with `mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). + +## Learn more + + * Official website: https://www.phoenixframework.org/ + * Guides: https://hexdocs.pm/phoenix/overview.html + * Docs: https://hexdocs.pm/phoenix + * Forum: https://elixirforum.com/c/phoenix-forum + * Source: https://github.com/phoenixframework/phoenix diff --git a/apps/ntfy_proxy/lib/ntfy_proxy.ex b/apps/ntfy_proxy/lib/ntfy_proxy.ex new file mode 100644 index 00000000..c3d3517c --- /dev/null +++ b/apps/ntfy_proxy/lib/ntfy_proxy.ex @@ -0,0 +1,75 @@ +defmodule NtfyProxy do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, views, channels and so on. + + This can be used in your application as: + + use NtfyProxy, :controller + use NtfyProxy, :view + + The definitions below will be executed for every view, + controller, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define any helper function in modules + and import those modules here. + """ + + def controller do + quote do + use Phoenix.Controller, namespace: NtfyProxy + + import Plug.Conn + alias NtfyProxy.Router.Helpers, as: Routes + end + end + + def view do + quote do + use Phoenix.View, + root: "lib/ntfy_proxy/templates", + namespace: NtfyProxy + + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1] + + # Include shared imports and aliases for views + unquote(view_helpers()) + end + end + + def router do + quote do + use Phoenix.Router + + import Plug.Conn + import Phoenix.Controller + end + end + + def channel do + quote do + use Phoenix.Channel + end + end + + defp view_helpers do + quote do + # Import basic rendering functionality (render, render_layout, etc) + import Phoenix.View + + import NtfyProxy.ErrorHelpers + alias NtfyProxy.Router.Helpers, as: Routes + end + end + + @doc """ + When used, dispatch to the appropriate controller/view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/apps/ntfy_proxy/lib/ntfy_proxy/application.ex b/apps/ntfy_proxy/lib/ntfy_proxy/application.ex new file mode 100644 index 00000000..822a5ea5 --- /dev/null +++ b/apps/ntfy_proxy/lib/ntfy_proxy/application.ex @@ -0,0 +1,32 @@ +defmodule NtfyProxy.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 = [ + # Start the Telemetry supervisor + NtfyProxy.Telemetry, + # Start the Endpoint (http/https) + NtfyProxy.Endpoint + # Start a worker by calling: NtfyProxy.Worker.start_link(arg) + # {NtfyProxy.Worker, arg} + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: NtfyProxy.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + @impl true + def config_change(changed, _new, removed) do + NtfyProxy.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/apps/ntfy_proxy/lib/ntfy_proxy/controllers/ntfy_proxy_controller.ex b/apps/ntfy_proxy/lib/ntfy_proxy/controllers/ntfy_proxy_controller.ex new file mode 100644 index 00000000..3b5783c7 --- /dev/null +++ b/apps/ntfy_proxy/lib/ntfy_proxy/controllers/ntfy_proxy_controller.ex @@ -0,0 +1,116 @@ +defmodule NtfyProxy.NtfyProxyController do + use NtfyProxy, :controller + import Plug.Conn + + require Logger + + def json(conn, _params) do + dispatch_stream(conn, true) + end + + def client(conn, long_polling? \\ false) do + conn.method + |> String.downcase() + |> String.to_existing_atom() + |> :hackney.request( + uri(conn), + conn.req_headers, + :stream, + connect_timeout: req_timeout(long_polling?), + recv_timeout: req_timeout(long_polling?), + ssl_options: [], + max_redirect: 5 + ) + end + + def dispatch_stream(%Plug.Conn{} = conn, long_polling? \\ false) do + {:ok, client} = client(conn, long_polling?) + + conn + |> write_proxy(client) + |> stream_proxy(client) + end + + def dispatch(%Plug.Conn{} = conn) do + {:ok, status, _headers, client} = + conn.method + |> String.downcase() + |> String.to_existing_atom() + |> :hackney.request( + uri(conn), + conn.req_headers, + conn.private[:raw_body] + ) + + {:ok, body} = :hackney.body(client) + send_resp(conn, status, body) + end + + def write_proxy(conn, client) do + Logger.debug("Dispatching body : #{conn.private[:raw_body]}.") + :hackney.send_body(client, conn.private[:raw_body]) + conn + end + + def read_proxy(conn, client) do + Logger.debug("Starting response now.") + + case :hackney.start_response(client) do + {:ok, status, headers, _client} -> + Logger.debug("Proxy response :ok. Status : #{status}") + {:ok, res_body} = :hackney.body(client) + send_resp(%{conn | resp_headers: headers}, status, res_body) + + {:ok, _client_ref} -> + Logger.debug("Got a client ref message ?") + send_resp(conn, 200, "") + + {:error, message} -> + Logger.debug("Timeout") + send_resp(conn, 408, Atom.to_string(message)) + end + end + + def stream_proxy(conn, client) do + case :hackney.start_response(client) do + {:ok, status, headers, _client} -> + Logger.debug("Proxy response :ok. Status : #{status} ; #{inspect(headers)}") + + %{conn | resp_headers: headers} + |> IO.inspect() + |> send_chunked(status) + |> stream_resp(client) + + {:error, message} -> + send_resp(conn, 408, Atom.to_string(message)) + end + end + + def stream_resp(conn, client) do + case :hackney.stream_body(client) do + {:ok, part} -> + Logger.debug("New chunk sent : #{part}") + chunk(conn, part) + stream_resp(conn, client) + + :done -> + conn + end + end + + def uri(conn) do + "#{proxy()}#{conn.request_path}?#{conn.query_string}" + end + + def proxy() do + "localhost:8001" + end + + def req_timeout(long_polling? \\ false) do + if long_polling? do + :infinity + else + 5000 + end + end +end diff --git a/apps/ntfy_proxy/lib/ntfy_proxy/endpoint.ex b/apps/ntfy_proxy/lib/ntfy_proxy/endpoint.ex new file mode 100644 index 00000000..699db813 --- /dev/null +++ b/apps/ntfy_proxy/lib/ntfy_proxy/endpoint.ex @@ -0,0 +1,51 @@ +defmodule NtfyProxy.Endpoint do + use Phoenix.Endpoint, otp_app: :ntfy_proxy + + # 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: "_ntfy_proxy_key", + signing_salt: "EMya6N1u" + ] + + # socket "/live", Phoenix.LiveView.Socket, websocket: [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: :ntfy_proxy, + gzip: false, + only: ~w(assets fonts images favicon.ico robots.txt) + + # Code reloading can be explicitly enabled under the + # :code_reloader configuration of your endpoint. + if code_reloading? do + plug Phoenix.CodeReloader + end + + plug Plug.RequestId + plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + + def copy_req_body(conn, _) do + {:ok, body, _conn} = Plug.Conn.read_body(conn) + + Plug.Conn.put_private(conn, :raw_body, body) + end + + plug :copy_req_body + + 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 NtfyProxy.Router +end diff --git a/apps/ntfy_proxy/lib/ntfy_proxy/router.ex b/apps/ntfy_proxy/lib/ntfy_proxy/router.ex new file mode 100644 index 00000000..e7779f7b --- /dev/null +++ b/apps/ntfy_proxy/lib/ntfy_proxy/router.ex @@ -0,0 +1,6 @@ +defmodule NtfyProxy.Router do + use NtfyProxy, :router + alias NtfyProxy.NtfyProxyController + + get("/:topic/json", NtfyProxyController, :json) +end diff --git a/apps/ntfy_proxy/lib/ntfy_proxy/telemetry.ex b/apps/ntfy_proxy/lib/ntfy_proxy/telemetry.ex new file mode 100644 index 00000000..7c81a94f --- /dev/null +++ b/apps/ntfy_proxy/lib/ntfy_proxy/telemetry.ex @@ -0,0 +1,48 @@ +defmodule NtfyProxy.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.stop.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.stop.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + + # 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. + # {NtfyProxy, :count_users, []} + ] + end +end diff --git a/apps/ntfy_proxy/lib/ntfy_proxy/views/error_helpers.ex b/apps/ntfy_proxy/lib/ntfy_proxy/views/error_helpers.ex new file mode 100644 index 00000000..0441f3bf --- /dev/null +++ b/apps/ntfy_proxy/lib/ntfy_proxy/views/error_helpers.ex @@ -0,0 +1,16 @@ +defmodule NtfyProxy.ErrorHelpers do + @moduledoc """ + Conveniences for translating and building error messages. + """ + + @doc """ + Translates an error message. + """ + def translate_error({msg, opts}) do + # Because the error messages we show in our forms and APIs + # are defined inside Ecto, we need to translate them dynamically. + Enum.reduce(opts, msg, fn {key, value}, acc -> + String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end) + end) + end +end diff --git a/apps/ntfy_proxy/lib/ntfy_proxy/views/error_view.ex b/apps/ntfy_proxy/lib/ntfy_proxy/views/error_view.ex new file mode 100644 index 00000000..63ba0816 --- /dev/null +++ b/apps/ntfy_proxy/lib/ntfy_proxy/views/error_view.ex @@ -0,0 +1,16 @@ +# defmodule NtfyProxy.ErrorView do +# use NtfyProxy, :view + +# # If you want to customize a particular status code +# # for a certain format, you may uncomment below. +# # 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 template_not_found(template, _assigns) do +# %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} +# end +# end diff --git a/apps/ntfy_proxy/lib/ntfy_socket.ex b/apps/ntfy_proxy/lib/ntfy_socket.ex new file mode 100644 index 00000000..3d14fda4 --- /dev/null +++ b/apps/ntfy_proxy/lib/ntfy_socket.ex @@ -0,0 +1,100 @@ +defmodule NtfyProxy.NtfySocket do + @moduledoc """ + Simple Websocket handler that echos back any data it receives + """ + + require Logger + + def proxy_path(req) do + '#{req.path}?#{req.qs}' + end + + def proxy_host() do + 'localhost' + end + + def proxy_port() do + 8001 + end + + # Tells the compiler we implement the `cowboy_websocket` + # behaviour. This will give warnings if our + # return types are notably incorrect or if we forget to implement a function. + # FUN FACT: when you `use MyAppWeb, :channel` in your normal Phoenix channel + # implementations, this is done under the hood for you. + @behaviour :cowboy_websocket + + # entry point of the websocket socket. + # WARNING: this is where you would need to do any authentication + # and authorization. Since this handler is invoked BEFORE + # our Phoenix router, it will NOT follow your pipelines defined there. + # + # WARNING: this function is NOT called in the same process context as the rest of the functions + # defined in this module. This is notably dissimilar to other gen_* behaviours. + @impl :cowboy_websocket + def init(req, _opts) do + proxy_url = proxy_path(req) + + {:cowboy_websocket, req, [proxy_path: proxy_url]} + end + + # as long as `init/2` returned `{:cowboy_websocket, req, opts}` + # this function will be called. You can begin sending packets at this point. + # We'll look at how to do that in the `websocket_handle` function however. + # This function is where you might want to implement `Phoenix.Presence`, schedule an `after_join` message etc. + @impl :cowboy_websocket + def websocket_init(opts) do + Logger.debug("Init websocket client") + + connect_opts = %{ + connect_timeout: :timer.minutes(1), + retry: 10, + retry_timeout: 100, + protocols: [:http] + } + + with {:ok, gun} <- :gun.open(proxy_host(), proxy_port(), connect_opts), + {:ok, protocol} <- :gun.await_up(gun), + stream <- :gun.ws_upgrade(gun, Keyword.get(opts, :proxy_path), []) do + state = %{gun: gun, protocol: protocol, stream: stream} + {[], state} + end + end + + # `websocket_handle` is where data from a client will be received. + # a `frame` will be delivered in one of a few shapes depending on what the client sent: + # + # :ping + # :pong + # {:text, data} + # {:binary, data} + # + # Similarly, the return value of this function is similar: + # + # {[reply_frame1, reply_frame2, ....], state} + # + # where `reply_frame` is the same format as what is delivered. + @impl :cowboy_websocket + # :ping is not handled for us like in Phoenix Channels. + # We must explicitly send :pong messages back. + + def websocket_handle(data, %{gun: gun} = state) do + Logger.info("Dispatch data TO the WS Provider : #{inspect(data)}") + :gun.ws_send(gun, data) + {[], state} + end + + # This function is where we will process all *other* messages that get delivered to the + # process mailbox. This function isn't used in this handler. + @impl :cowboy_websocket + def websocket_info({:gun_ws, _pid, _ref, data}, state) do + Logger.info("Dispatch data To the WS Distributor : #{inspect(data)}") + + {[data], state} + end + + def websocket_info(info, state) do + Logger.debug("Ntfy Proxy ws info : #{inspect(info)}") + {[], state} + end +end diff --git a/apps/ntfy_proxy/mix.exs b/apps/ntfy_proxy/mix.exs new file mode 100644 index 00000000..21eb0ef0 --- /dev/null +++ b/apps/ntfy_proxy/mix.exs @@ -0,0 +1,60 @@ +defmodule NtfyProxy.MixProject do + use Mix.Project + + def project do + [ + app: :ntfy_proxy, + version: "0.1.0", + build_path: "../../_build", + config_path: "../../config/config.exs", + deps_path: "../../deps", + lockfile: "../../mix.lock", + elixir: "~> 1.12", + elixirc_paths: elixirc_paths(Mix.env()), + compilers: Mix.compilers(), + start_permanent: Mix.env() == :prod, + aliases: aliases(), + deps: deps() + ] + end + + # Configuration for the OTP application. + # + # Type `mix help compile.app` for more information. + def application do + [ + mod: {NtfyProxy.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Specifies your project dependencies. + # + # Type `mix help deps` for examples and options. + defp deps do + [ + {:phoenix, "~> 1.6.15"}, + {:telemetry, "~> 0.4.3", override: true}, + {:telemetry_metrics, "~> 0.4"}, + {:telemetry_poller, "~> 0.4"}, + {:plug_cowboy, "~> 2.0"}, + {:hackney, "~> 1.18"}, + {:gun, "~> 1.3"}, + {:cowlib, "~> 2.11.0", override: true}, + {:lenra, in_umbrella: true} + ] + end + + # Aliases are shortcuts or tasks specific to the current project. + # + # See the documentation for `Mix` for more info on aliases. + defp aliases do + [ + setup: ["deps.get"] + ] + end +end diff --git a/apps/ntfy_proxy/test/ntfy_proxy/views/error_view_test.exs b/apps/ntfy_proxy/test/ntfy_proxy/views/error_view_test.exs new file mode 100644 index 00000000..9238400c --- /dev/null +++ b/apps/ntfy_proxy/test/ntfy_proxy/views/error_view_test.exs @@ -0,0 +1,15 @@ +defmodule NtfyProxy.ErrorViewTest do + use NtfyProxy.ConnCase, async: true + + # Bring render/3 and render_to_string/3 for testing custom views + import Phoenix.View + + test "renders 404.json" do + assert render(NtfyProxy.ErrorView, "404.json", []) == %{errors: %{detail: "Not Found"}} + end + + test "renders 500.json" do + assert render(NtfyProxy.ErrorView, "500.json", []) == + %{errors: %{detail: "Internal Server Error"}} + end +end diff --git a/apps/ntfy_proxy/test/support/channel_case.ex b/apps/ntfy_proxy/test/support/channel_case.ex new file mode 100644 index 00000000..657f3a9e --- /dev/null +++ b/apps/ntfy_proxy/test/support/channel_case.ex @@ -0,0 +1,34 @@ +defmodule NtfyProxy.ChannelCase do + @moduledoc """ + This module defines the test case to be used by + channel tests. + + Such tests rely on `Phoenix.ChannelTest` and also + import other functionality to make it easier + to build common data structures and query the data layer. + + Finally, if the test case interacts with the database, + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use NtfyProxy.ChannelCase, async: true`, although + this option is not recommended for other databases. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # Import conveniences for testing with channels + import Phoenix.ChannelTest + import NtfyProxy.ChannelCase + + # The default endpoint for testing + @endpoint NtfyProxy.Endpoint + end + end + + setup _tags do + :ok + end +end diff --git a/apps/ntfy_proxy/test/support/conn_case.ex b/apps/ntfy_proxy/test/support/conn_case.ex new file mode 100644 index 00000000..80aa9d90 --- /dev/null +++ b/apps/ntfy_proxy/test/support/conn_case.ex @@ -0,0 +1,37 @@ +defmodule NtfyProxy.ConnCase do + @moduledoc """ + This module defines the test case to be used by + tests that require setting up a connection. + + Such tests rely on `Phoenix.ConnTest` and also + import other functionality to make it easier + to build common data structures and query the data layer. + + Finally, if the test case interacts with the database, + we enable the SQL sandbox, so changes done to the database + are reverted at the end of every test. If you are using + PostgreSQL, you can even run database tests asynchronously + by setting `use NtfyProxy.ConnCase, async: true`, although + this option is not recommended for other databases. + """ + + use ExUnit.CaseTemplate + + using do + quote do + # Import conveniences for testing with connections + import Plug.Conn + import Phoenix.ConnTest + import NtfyProxy.ConnCase + + alias NtfyProxy.Router.Helpers, as: Routes + + # The default endpoint for testing + @endpoint NtfyProxy.Endpoint + end + end + + setup _tags do + {:ok, conn: Phoenix.ConnTest.build_conn()} + end +end diff --git a/apps/ntfy_proxy/test/test_helper.exs b/apps/ntfy_proxy/test/test_helper.exs new file mode 100644 index 00000000..869559e7 --- /dev/null +++ b/apps/ntfy_proxy/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/config/config.exs b/config/config.exs index a183d8ee..85e56a15 100644 --- a/config/config.exs +++ b/config/config.exs @@ -12,6 +12,25 @@ # General application configuration import Config + +config :ntfy_proxy, + generators: [context_app: false] + +# Configures the endpoint +dispatch = [ + _: [ + {"/:topic/ws", NtfyProxy.NtfySocket, []}, + {:_, Phoenix.Endpoint.Cowboy2Handler, {NtfyProxy.Endpoint, []}} + ] +] + +config :ntfy_proxy, NtfyProxy.Endpoint, + url: [host: "localhost"], + http: [port: {:system, "PORT"}, dispatch: dispatch], + render_errors: [view: NtfyProxy.ErrorView, accepts: ~w(json), layout: false], + pubsub_server: NtfyProxy.PubSub, + live_view: [signing_salt: "FVzjWv3z"] + # Configure the repo config :lenra, ecto_repos: [Lenra.Repo] @@ -73,9 +92,9 @@ config :application_runner, lenra_environment_table: "environments", lenra_user_table: "users", repo: Lenra.Repo, - url: System.get_env("HOST", "4000"), - faas_url: System.get_env("FAAS_URL", "https://openfaas-dev.lenra.me"), - faas_auth: System.get_env("FAAS_AUTH", "Basic YWRtaW46Z0Q4VjNHR1YxeUpS"), + url: System.get_env("HOST", "http://bf4a-2803-9810-40cf-7910-00-c78.ngrok.io"), + faas_url: System.get_env("FAAS_URL", "http://localhost:8080"), + faas_auth: System.get_env("FAAS_AUTH", "Basic YWRtaW46c0c1Y1ZKZFg0dXBx"), faas_registry: System.get_env("FAAS_REGISTRY", "registry.gitlab.com/lenra/platform/lenra-ci"), env: System.get_env("ENVIRONMENT", "dev"), listeners_timeout: 1 * 60 * 60 * 1000, diff --git a/config/dev.exs b/config/dev.exs index ca990ada..9459db83 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -2,6 +2,23 @@ # DO NOT USE THESE SECRET ON PRODUCTION ! import Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we use it +# with esbuild to bundle .js and .css sources. +config :ntfy_proxy, NtfyProxy.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {0, 0, 0, 0}, port: 4100], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "xWrF2kbHKL2hs392U07ahvIWKAhZqT1KJnkzc1z3WCfW91DAmk9m9ZUbQPvfKPJo", + watchers: [] + # Configure your database config :lenra, Lenra.Repo, username: System.get_env("POSTGRES_USER", "postgres"), @@ -59,10 +76,10 @@ config :phoenix, :stacktrace_depth, 20 config :phoenix, :plug_init_mode, :runtime config :lenra, - faas_url: System.get_env("FAAS_URL", "https://openfaas-dev.lenra.me"), - faas_auth: System.get_env("FAAS_AUTH", "Basic YWRtaW46Z0Q4VjNHR1YxeUpS"), + faas_url: System.get_env("FAAS_URL", "http://localhost:8080"), + faas_auth: System.get_env("FAAS_AUTH", "Basic YWRtaW46c0c1Y1ZKZFg0dXBx"), faas_registry: System.get_env("FAAS_REGISTRY", "registry.gitlab.com/lenra/platform/lenra-ci"), - runner_callback_url: System.get_env("LOCAL_TUNNEL_URL"), + runner_callback_url: System.get_env("LOCAL_TUNNEL_URL", "http://bf4a-2803-9810-40cf-7910-00-c78.ngrok.io"), lenra_env: "dev", gitlab_api_url: System.get_env("GITLAB_API_URL", "https://gitlab.com/api/v4"), gitlab_api_token: System.get_env("GITLAB_API_TOKEN", "Zuz-dZc834q3CtU-bnX5"), @@ -81,5 +98,6 @@ config :lenra, config :lenra, Lenra.Mailer, sandbox: true, api_key: System.get_env("SENDGRID_API_KEY") config :cors_plug, - origin: System.get_env("ALLOWED_CLIENT_ORIGINS", "http://localhost:10000") |> String.split(","), + origin: + System.get_env("ALLOWED_CLIENT_ORIGINS", "http://localhost:10000,http://localhost:10001") |> String.split(","), methods: ["GET", "POST", "PUT", "PATCH", "OPTION"] diff --git a/config/prod.exs b/config/prod.exs index 5507fdd3..d71460e2 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -2,6 +2,20 @@ # There is NO important data here, all secret/passwords and dynamic config are stored in releases.exs import Config + +# For production, don't forget to configure the url host +# to something meaningful, Phoenix uses this information +# when generating URLs. +# +# Note we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the `mix phx.digest` task, +# which you should run after static files are built and +# before starting your production server. +config :ntfy_proxy, NtfyProxy.Endpoint, + # url: [host: "example.com", port: 80], + cache_static_manifest: "priv/static/cache_manifest.json" + # For production, don't forget to configure the url host # to something meaningful, Phoenix uses this information # when generating URLs. diff --git a/config/releases.exs b/config/releases.exs index da157a48..f85395f9 100644 --- a/config/releases.exs +++ b/config/releases.exs @@ -8,6 +8,15 @@ config :lenra_web, LenraWeb.Endpoint, secret_key_base: System.fetch_env!("SECRET_KEY_BASE"), url: [host: System.fetch_env!("API_ENDPOINT"), port: System.fetch_env!("PORT")] +config :ntfy_proxy, NtfyProxy.Endpoint, + http: [ + # Enable IPv6 and bind on all interfaces. + # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. + ip: {0, 0, 0, 0, 0, 0, 0, 0}, + port: String.to_integer(System.get_env("NTFY_PROXY_PORT")) + ], + secret_key_base: System.fetch_env!("NTFY_SECRET_KEY_BASE") + config :lenra, Lenra.Repo, username: System.fetch_env!("POSTGRES_USER"), password: System.fetch_env!("POSTGRES_PASSWORD"), diff --git a/config/test.exs b/config/test.exs index 94bf102e..8a5dffc1 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,4 +1,12 @@ import Config + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :ntfy_proxy, NtfyProxy.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 4102], + secret_key_base: "qralrxQCgcf2rruZmpCDPgaM+81vbcZlqBzeZ8unzz7fL8NfVxXii/6QNWJZQBBm", + server: false + # We don't run a server during test. If one is required, # you can enable the server option below. config :lenra_web, LenraWeb.Endpoint, diff --git a/mix.lock b/mix.lock index 50b66f5a..4de67a6f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,4 @@ %{ - "application_runner": {:git, "https://github.com/lenra-io/application-runner.git", "79d71b7d1dce340444dfad610ff7361766a80c13", [ref: "v1.0.0-beta.102", submodules: true]}, "argon2_elixir": {:hex, :argon2_elixir, "2.4.1", "edb27bdd326bc738f3e4614eddc2f73507be6fedc9533c6bcc6f15bbac9c85cc", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "0e21f52a373739d00bdfd5fe6da2f04eea623cb4f66899f7526dd9db03903d9f"}, "bamboo": {:hex, :bamboo, "2.2.0", "f10a406d2b7f5123eb1f02edfa043c259db04b47ab956041f279eaac776ef5ce", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8c3b14ba7d2f40cb4be04128ed1e2aff06d91d9413d38bafb4afccffa3ade4fc"}, "bamboo_smtp": {:hex, :bamboo_smtp, "4.2.2", "e9f57a2300df9cb496c48751bd7668a86a2b89aa2e79ccaa34e0c46a5f64c3ae", [:mix], [{:bamboo, "~> 2.2.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 1.2.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm", "28cac2ec8adaae02aed663bf68163992891a3b44cfd7ada0bebe3e09bed7207f"}, @@ -7,7 +6,7 @@ "bouncer": {:git, "https://github.com/lenra-io/bouncer.git", "50f11ed26dd7730ba405afedeec65a00499c8cee", [tag: "v1.0.0"]}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, - "castore": {:hex, :castore, "0.1.19", "a2c3e46d62b7f3aa2e6f88541c21d7400381e53704394462b9fd4f06f6d42bb6", [:mix], [], "hexpm", "e96e0161a5dc82ef441da24d5fa74aefc40d920f3a6645d15e1f9f3e66bb2109"}, + "castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, @@ -17,38 +16,36 @@ "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, "crontab": {:hex, :crontab, "1.1.13", "3bad04f050b9f7f1c237809e42223999c150656a6b2afbbfef597d56df2144c5", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "d67441bec989640e3afb94e123f45a2bc42d76e02988c9613885dc3d01cf7085"}, - "db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"}, + "db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "deferred_config": {:hex, :deferred_config, "0.1.1", "ec912e9ee3c99b90a8d4bdec8fbd15309f4bd6729f30789e0ff6f595d06bbce5", [:mix], [], "hexpm", "2eb5311037feb4a6a5dbe3ecc5c98af7ea849730e5dbd9aee0f45c5dbccc3922"}, "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, - "earmark": {:hex, :earmark, "1.4.33", "2b33a505180583f98bfa17317f03973b52081bdb24a11be05a7f4fa6d64dd8bf", [:mix], [{:earmark_parser, "~> 1.4.29", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "21b31363d6a0a70802cfbaf2de88355778aa76654298a072bce2e01d1858ae06"}, + "earmark": {:hex, :earmark, "1.4.34", "d7f89d3bbd7567a0bffc465e0a949f8f8dcbe43909c3acf96f4761a302cea10c", [:mix], [{:earmark_parser, "~> 1.4.29", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "90b106f3dad85b133b10d7d628167c88246123fd1cecb4557d83d21ec9e65504"}, "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, - "ecto": {:hex, :ecto, "3.8.4", "e06b8b87e62b27fea17fd2ff6041572ddd10339fd16cdf58446e402c6c90a74b", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f9244288b8d42db40515463a008cf3f4e0e564bb9c249fe87bf28a6d79fe82d4"}, - "ecto_sql": {:hex, :ecto_sql, "3.8.3", "a7d22c624202546a39d615ed7a6b784580391e65723f2d24f65941b4dd73d471", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.8.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "348cb17fb9e6daf6f251a87049eafcb57805e2892e5e6a0f5dea0985d367329b"}, - "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, + "ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"}, + "ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"}, + "elixir_make": {:hex, :elixir_make, "0.7.3", "c37fdae1b52d2cc51069713a58c2314877c1ad40800a57efb213f77b078a460d", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "24ada3e3996adbed1fa024ca14995ef2ba3d0d17b678b0f3f2b1f66e6ce2b274"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "event_queue": {:git, "https://github.com/lenra-io/event-queue.git", "3278e1224b63f51cbb7f574894d67ced104e928f", [tag: "v1.0.0"]}, "ex_component_schema": {:git, "https://github.com/lenra-io/ex_component_schema", "ceaf372e18e4c06360501f4e6152a0ea567fcfaa", [ref: "v1.0.0-beta.3"]}, - "ex_doc": {:hex, :ex_doc, "0.29.0", "4a1cb903ce746aceef9c1f9ae8a6c12b742a5461e6959b9d3b24d813ffbea146", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "f096adb8bbca677d35d278223361c7792d496b3fc0d0224c9d4bc2f651af5db1"}, - "excoveralls": {:hex, :excoveralls, "0.15.0", "ac941bf85f9f201a9626cc42b2232b251ad8738da993cf406a4290cacf562ea4", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9631912006b27eca30a2f3c93562bc7ae15980afb014ceb8147dc5cdd8f376f1"}, + "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, + "excoveralls": {:hex, :excoveralls, "0.15.3", "54bb54043e1cf5fe431eb3db36b25e8fd62cf3976666bafe491e3fa5e29eba47", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f8eb5d8134d84c327685f7bb8f1db4147f1363c3c9533928234e496e3070114e"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "finch": {:hex, :finch, "0.13.0", "c881e5460ec563bf02d4f4584079e62201db676ed4c0ef3e59189331c4eddf7b", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "49957dcde10dcdc042a123a507a9c5ec5a803f53646d451db2f7dea696fba6cc"}, + "finch": {:hex, :finch, "0.14.0", "619bfdee18fc135190bf590356c4bf5d5f71f916adb12aec94caa3fa9267a4bc", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5459acaf18c4fdb47a8c22fb3baff5d8173106217c8e56c5ba0b93e66501a8dd"}, "gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"}, - "gen_stage": {:hex, :gen_stage, "1.1.2", "b1656cd4ba431ed02c5656fe10cb5423820847113a07218da68eae5d6a260c23", [:mix], [], "hexpm", "9e39af23140f704e2b07a3e29d8f05fd21c2aaf4088ff43cb82be4b9e3148d02"}, "gen_state_machine": {:hex, :gen_state_machine, "2.1.0", "a38b0e53fad812d29ec149f0d354da5d1bc0d7222c3711f3a0bd5aa608b42992", [:mix], [], "hexpm", "ae367038808db25cee2f2c4b8d0531522ea587c4995eb6f96ee73410a60fa06b"}, - "guardian": {:hex, :guardian, "2.1.2", "bfa5f472319f227e0e72d156fa5f7e82dc32043934abf9aa86a1eeab2904849d", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "76aafc19311e0cc1e9c26bab7707680407a85df7187b9e3ad4e48890e3600954"}, + "guardian": {:hex, :guardian, "2.3.1", "2b2d78dc399a7df182d739ddc0e566d88723299bfac20be36255e2d052fd215d", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bbe241f9ca1b09fad916ad42d6049d2600bbc688aba5b3c4a6c82592a54274c3"}, "guardian_db": {:hex, :guardian_db, "2.1.0", "ec95a9d99cdd1e550555d09a7bb4a340d8887aad0697f594590c2fd74be02426", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f8e7d543ac92c395f3a7fd5acbe6829faeade57d688f7562e2f0fca8f94a0d70"}, - "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, + "gun": {:hex, :gun, "1.3.3", "cf8b51beb36c22b9c8df1921e3f2bc4d2b1f68b49ad4fbc64e91875aa14e16b4", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "3106ce167f9c9723f849e4fb54ea4a4d814e3996ae243a1c828b256e749041e0"}, + "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, "honeydew": {:hex, :honeydew, "1.5.0", "53088c1d87399efa5c0939adc8d32a9713b8fe6ce00a77c6769d2d363abac6bc", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "f71669e25f6a972e970ecbd79c34c4ad4b28369be78e4f8164fe8d0c5a674907"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, - "hut": {:hex, :hut, "1.3.0", "71f2f054e657c03f959cf1acc43f436ea87580696528ca2a55c8afb1b06c85e7", [:"erlang.mk", :rebar, :rebar3], [], "hexpm", "7e15d28555d8a1f2b5a3a931ec120af0753e4853a4c66053db354f35bf9ab563"}, - "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, - "jose": {:hex, :jose, "1.11.2", "f4c018ccf4fdce22c71e44d471f15f723cb3efab5d909ab2ba202b5bf35557b3", [:mix, :rebar3], [], "hexpm", "98143fbc48d55f3a18daba82d34fe48959d44538e9697c08f34200fa5f0947d2"}, + "jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"}, "json_diff": {:hex, :json_diff, "0.1.3", "c80d5ca5416e785867e765e906e9a91b7efc35bfd505af276654d108f4995736", [:mix], [], "hexpm", "a5332e8293e7e9f384d34ea44645d7961334db73739165178fd4a7728d06f7d1"}, - "lenra_common": {:git, "https://github.com/lenra-io/lenra-common.git", "73e710692b5d52816827e7156af39ab7e0caf0f0", [tag: "v2.4.0"]}, - "libcluster": {:hex, :libcluster, "3.3.1", "e7a4875cd1290cee7a693d6bd46076863e9e433708b01339783de6eff5b7f0aa", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b575ca63c1cd84e01f3fa0fc45e6eb945c1ee7ae8d441d33def999075e9e5398"}, + "lenra_common": {:git, "https://github.com/lenra-io/lenra-common.git", "bbcc132e27426835de1227d0cd12048bc31746ef", [tag: "v2.5.0"]}, + "libcluster": {:hex, :libcluster, "3.3.2", "84c6ebfdc72a03805955abfb5ff573f71921a3e299279cc3445445d5af619ad1", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8b691ce8185670fc8f3fc0b7ed59eff66c6889df890d13411f8f1a0e6871d8a5"}, "libring": {:hex, :libring, "1.6.0", "d5dca4bcb1765f862ab59f175b403e356dec493f565670e0bacc4b35e109ce0d", [:mix], [], "hexpm", "5e91ece396af4bce99953d49ee0b02f698cd38326d93cd068361038167484319"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, @@ -57,26 +54,26 @@ "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"}, - "mongodb_driver": {:hex, :mongodb_driver, "0.9.2", "ad7d967536db2ec1da9dc866d00b003396202233acaeb9dc6cc8492c1d77118b", [:mix], [{:db_connection, "~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7986fbcc1ecc2af1ac7fdaa94252ce2cea4fa49c0a9d94c97d464969e033e2c7"}, + "mongodb_driver": {:hex, :mongodb_driver, "1.0.2", "66a119a512110e9c4a175d9ece33cbfee25c17c7100fe8c911ca95ffe9c5f250", [:mix], [{:db_connection, "~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d728f502b8af2dacedb52ace9d983e81674d79858c13feda804f7aee37f0dad0"}, "neotomex": {:hex, :neotomex, "0.1.7", "64f76513653aa87ea7abdde0fd600e56955d838020a13d88f2bf334c88ac3e7a", [:mix], [], "hexpm", "4b87b8f614d1cd89dc8ba80ba0e559bedb3ebf6f6d74cd774fcfdd215e861445"}, - "nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"}, + "nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, - "peerage": {:hex, :peerage, "1.0.3", "945c3dfc407215b89682c65198d004028df0fa772bfea4d2cc9bb4e39e8be9a0", [:mix], [{:deferred_config, "~> 0.1.1", [hex: :deferred_config, repo: "hexpm", optional: false]}], "hexpm", "c9a3316be955f65da1ec39ef891b4c15f2f13bec7bd8d84ef3cdc9fd633d889b"}, - "phoenix": {:hex, :phoenix, "1.5.14", "2d5db884be496eefa5157505ec0134e66187cb416c072272420c5509d67bf808", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "207f1aa5520320cbb7940d7ff2dde2342162cf513875848f88249ea0ba02fef7"}, + "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_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"}, "phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.5", "1495bb014be12c9a9252eca04b9af54246f6b5c1e4cd1f30210cd00ec540cf8e", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "ef4fa50dd78364409039c99cf6f98ab5209b4c5f8796c17f4db118324f0db852"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.7", "05a42377075868a678d446361effba80cefef19ab98941c01a7a4c7560b29121", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25eaf41028eb351b90d4f69671874643a09944098fefd0d01d442f40a6091b6f"}, "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_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, - "postgrex": {:hex, :postgrex, "0.15.13", "7794e697481799aee8982688c261901de493eb64451feee6ea58207d7266d54a", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "3ffb76e1a97cfefe5c6a95632a27ffb67f28871c9741fb585f9d1c3cd2af70f1"}, - "quantum": {:hex, :quantum, "3.5.0", "8d2c5ba68c55991e8975aca368e3ab844ba01f4b87c4185a7403280e2c99cf34", [:mix], [{:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.14 or ~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_registry, "~> 0.2", [hex: :telemetry_registry, repo: "hexpm", optional: false]}], "hexpm", "cab737d1d9779f43cb1d701f46dd05ea58146fd96238d91c9e0da662c1982bb6"}, - "query_parser": {:git, "https://github.com/lenra-io/query-parser.git", "c4d37a7da86292d9e2729d91084574c7fbc7ad32", [tag: "v1.0.0-beta.15"]}, + "postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, + "query_parser": {:git, "https://github.com/lenra-io/query-parser.git", "dd470cd55bb7c12f21153886de6080755ccffd89", [tag: "v1.0.0-beta.17"]}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "sentry": {:hex, :sentry, "8.0.6", "c8de1bf0523bc120ec37d596c55260901029ecb0994e7075b0973328779ceef7", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.3", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "051a2d0472162f3137787c7c9d6e6e4ef239de9329c8c45b1f1bf1e9379e1883"}, "simplehttp": {:hex, :simplehttp, "0.5.1", "103d027c50398b1e2cf26329cd78d8cf55211c17d19e0bb258a7987fe8df3584", [:mix], [], "hexpm", "32a945235f59cdd6615478f143807f79416555559bf0a701971570628a6884f1"}, @@ -87,6 +84,5 @@ "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"}, - "telemetry_registry": {:hex, :telemetry_registry, "0.3.0", "6768f151ea53fc0fbca70dbff5b20a8d663ee4e0c0b2ae589590e08658e76f1e", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "492e2adbc609f3e79ece7f29fec363a97a2c484ac78a83098535d6564781e917"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, }