Parcourir la source

Update project to Phoenix >=1.6 standards

theenglishway (time) il y a 4 ans
Parent
commit
4a0b0ce1d2

+ 2 - 4
README.md

@@ -57,7 +57,6 @@ The PostgreSQL role/database must be installed, if required.
 To start the server in dev mode : 
   
 * Install dependencies with `mix deps.get`
-* Install Node.js dependencies with `mix cmd npm install --prefix assets`
 * Start the server : `mix phx.server` or `iex -S mix phx.server` to run within a shell
 
 The server can now be accessed on [`localhost:4000`](http://localhost:4000).
@@ -69,9 +68,8 @@ In production mode some configuration is extracted from the environment.
 * Generate a secret key using `mix phx.gen.secret`
 * Install dependencies with `mix deps.get --only prod`
 * Compile the application `MIX_ENV=prod mix compile`
-* Compile static assets into `priv/static` : `npm run deploy --prefix ./assets`
-* Compress and digest static assets `mix phx.digest`
-* Start the server : `MIX_ENV=prod DATABASE_URL=db-url SECRET_KEY_BASE=secret_key PORT=port mix phx.server`
+* Compress and digest static assets `MIX_ENV=prod mix assets.digest`
+* Start the server : `MIX_ENV=prod DATABASE_URL=db-url SECRET_KEY_BASE=secret_key PORT=port HOST=host mix phx.server`
 
 ### Getting the latest release
 

+ 89 - 0
assets/css/app.css

@@ -0,0 +1,89 @@
+/* This file is for your main application CSS */
+@import "./phoenix.css";
+
+/* Alerts and form errors used by phx.new */
+.alert {
+  padding: 15px;
+  margin-bottom: 20px;
+  border: 1px solid transparent;
+  border-radius: 4px;
+}
+.alert-info {
+  color: #31708f;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+.alert-warning {
+  color: #8a6d3b;
+  background-color: #fcf8e3;
+  border-color: #faebcc;
+}
+.alert-danger {
+  color: #a94442;
+  background-color: #f2dede;
+  border-color: #ebccd1;
+}
+.alert p {
+  margin-bottom: 0;
+}
+.alert:empty {
+  display: none;
+}
+.invalid-feedback {
+  color: #a94442;
+  display: block;
+  margin: -1rem 0 2rem;
+}
+
+/* LiveView specific classes for your customization */
+.phx-no-feedback.invalid-feedback,
+.phx-no-feedback .invalid-feedback {
+  display: none;
+}
+
+.phx-click-loading {
+  opacity: 0.5;
+  transition: opacity 1s ease-out;
+}
+
+.phx-disconnected{
+  cursor: wait;
+}
+.phx-disconnected *{
+  pointer-events: none;
+}
+
+.phx-modal {
+  opacity: 1!important;
+  position: fixed;
+  z-index: 1;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  overflow: auto;
+  background-color: rgb(0,0,0);
+  background-color: rgba(0,0,0,0.4);
+}
+
+.phx-modal-content {
+  background-color: #fefefe;
+  margin: 15vh auto;
+  padding: 20px;
+  border: 1px solid #888;
+  width: 80%;
+}
+
+.phx-modal-close {
+  color: #aaa;
+  float: right;
+  font-size: 28px;
+  font-weight: bold;
+}
+
+.phx-modal-close:hover,
+.phx-modal-close:focus {
+  color: black;
+  text-decoration: none;
+  cursor: pointer;
+}

Fichier diff supprimé car celui-ci est trop grand
+ 3 - 3
assets/css/phoenix.css


+ 20 - 12
assets/js/app.js

@@ -1,21 +1,30 @@
-// We need to import the CSS so that webpack will load it.
-// The MiniCssExtractPlugin is used to separate it out into
-// its own CSS file.
-import "../css/app.scss"
+// We import the CSS which is extracted to its own file by esbuild.
+// Remove this line if you add a your own CSS build pipeline (e.g postcss).
+import "../css/app.css"
 
-// webpack automatically bundles all modules in your
-// entry points. Those entry points can be configured
-// in "webpack.config.js".
+// If you want to use Phoenix channels, run `mix help phx.gen.channel`
+// to get started and then uncomment the line below.
+// import "./user_socket.js"
+
+// You can include dependencies in two ways.
+//
+// The simplest option is to put them in assets/vendor and
+// import them using relative paths:
+//
+//     import "./vendor/some-package.js"
 //
-// Import deps with the dep name or local files with a relative path, for example:
+// Alternatively, you can `npm install some-package` and import
+// them using a path starting with the package name:
 //
-//     import {Socket} from "phoenix"
-//     import socket from "./socket"
+//     import "some-package"
 //
+
+// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
 import "phoenix_html"
+// Establish Phoenix Socket and LiveView configuration.
 import {Socket} from "phoenix"
-import topbar from "topbar"
 import {LiveSocket} from "phoenix_live_view"
+import topbar from "../vendor/topbar"
 
 let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
 let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
@@ -33,4 +42,3 @@ liveSocket.connect()
 // >> liveSocket.enableLatencySim(1000)  // enabled for duration of browser session
 // >> liveSocket.disableLatencySim()
 window.liveSocket = liveSocket
-

+ 157 - 0
assets/vendor/topbar.js

@@ -0,0 +1,157 @@
+/**
+ * @license MIT
+ * topbar 1.0.0, 2021-01-06
+ * http://buunguyen.github.io/topbar
+ * Copyright (c) 2021 Buu Nguyen
+ */
+(function (window, document) {
+  "use strict";
+
+  // https://gist.github.com/paulirish/1579671
+  (function () {
+    var lastTime = 0;
+    var vendors = ["ms", "moz", "webkit", "o"];
+    for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+      window.requestAnimationFrame =
+        window[vendors[x] + "RequestAnimationFrame"];
+      window.cancelAnimationFrame =
+        window[vendors[x] + "CancelAnimationFrame"] ||
+        window[vendors[x] + "CancelRequestAnimationFrame"];
+    }
+    if (!window.requestAnimationFrame)
+      window.requestAnimationFrame = function (callback, element) {
+        var currTime = new Date().getTime();
+        var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+        var id = window.setTimeout(function () {
+          callback(currTime + timeToCall);
+        }, timeToCall);
+        lastTime = currTime + timeToCall;
+        return id;
+      };
+    if (!window.cancelAnimationFrame)
+      window.cancelAnimationFrame = function (id) {
+        clearTimeout(id);
+      };
+  })();
+
+  var canvas,
+    progressTimerId,
+    fadeTimerId,
+    currentProgress,
+    showing,
+    addEvent = function (elem, type, handler) {
+      if (elem.addEventListener) elem.addEventListener(type, handler, false);
+      else if (elem.attachEvent) elem.attachEvent("on" + type, handler);
+      else elem["on" + type] = handler;
+    },
+    options = {
+      autoRun: true,
+      barThickness: 3,
+      barColors: {
+        0: "rgba(26,  188, 156, .9)",
+        ".25": "rgba(52,  152, 219, .9)",
+        ".50": "rgba(241, 196, 15,  .9)",
+        ".75": "rgba(230, 126, 34,  .9)",
+        "1.0": "rgba(211, 84,  0,   .9)",
+      },
+      shadowBlur: 10,
+      shadowColor: "rgba(0,   0,   0,   .6)",
+      className: null,
+    },
+    repaint = function () {
+      canvas.width = window.innerWidth;
+      canvas.height = options.barThickness * 5; // need space for shadow
+
+      var ctx = canvas.getContext("2d");
+      ctx.shadowBlur = options.shadowBlur;
+      ctx.shadowColor = options.shadowColor;
+
+      var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
+      for (var stop in options.barColors)
+        lineGradient.addColorStop(stop, options.barColors[stop]);
+      ctx.lineWidth = options.barThickness;
+      ctx.beginPath();
+      ctx.moveTo(0, options.barThickness / 2);
+      ctx.lineTo(
+        Math.ceil(currentProgress * canvas.width),
+        options.barThickness / 2
+      );
+      ctx.strokeStyle = lineGradient;
+      ctx.stroke();
+    },
+    createCanvas = function () {
+      canvas = document.createElement("canvas");
+      var style = canvas.style;
+      style.position = "fixed";
+      style.top = style.left = style.right = style.margin = style.padding = 0;
+      style.zIndex = 100001;
+      style.display = "none";
+      if (options.className) canvas.classList.add(options.className);
+      document.body.appendChild(canvas);
+      addEvent(window, "resize", repaint);
+    },
+    topbar = {
+      config: function (opts) {
+        for (var key in opts)
+          if (options.hasOwnProperty(key)) options[key] = opts[key];
+      },
+      show: function () {
+        if (showing) return;
+        showing = true;
+        if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);
+        if (!canvas) createCanvas();
+        canvas.style.opacity = 1;
+        canvas.style.display = "block";
+        topbar.progress(0);
+        if (options.autoRun) {
+          (function loop() {
+            progressTimerId = window.requestAnimationFrame(loop);
+            topbar.progress(
+              "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)
+            );
+          })();
+        }
+      },
+      progress: function (to) {
+        if (typeof to === "undefined") return currentProgress;
+        if (typeof to === "string") {
+          to =
+            (to.indexOf("+") >= 0 || to.indexOf("-") >= 0
+              ? currentProgress
+              : 0) + parseFloat(to);
+        }
+        currentProgress = to > 1 ? 1 : to;
+        repaint();
+        return currentProgress;
+      },
+      hide: function () {
+        if (!showing) return;
+        showing = false;
+        if (progressTimerId != null) {
+          window.cancelAnimationFrame(progressTimerId);
+          progressTimerId = null;
+        }
+        (function loop() {
+          if (topbar.progress("+.1") >= 1) {
+            canvas.style.opacity -= 0.05;
+            if (canvas.style.opacity <= 0.05) {
+              canvas.style.display = "none";
+              fadeTimerId = null;
+              return;
+            }
+          }
+          fadeTimerId = window.requestAnimationFrame(loop);
+        })();
+      },
+    };
+
+  if (typeof module === "object" && typeof module.exports === "object") {
+    module.exports = topbar;
+  } else if (typeof define === "function" && define.amd) {
+    define(function () {
+      return topbar;
+    });
+  } else {
+    this.topbar = topbar;
+  }
+}.call(this, window, document));

+ 24 - 3
config/config.exs

@@ -1,5 +1,5 @@
 # This file is responsible for configuring your application
-# and its dependencies with the aid of the Mix.Config module.
+# and its dependencies with the aid of the Config module.
 #
 # This configuration file is loaded before any dependency and
 # is restricted to this project.
@@ -13,10 +13,31 @@ config :toy,
 # Configures the endpoint
 config :toy, ToyWeb.Endpoint,
   url: [host: "localhost"],
-  secret_key_base: "fMJMY033bh8G6zjNAItIHEAbIp4VNB3XtUiftPhmnguMRsW3Jvs63KjPbKntzRrc",
   render_errors: [view: ToyWeb.ErrorView, accepts: ~w(html json), layout: false],
   pubsub_server: Toy.PubSub,
-  live_view: [signing_salt: "Mxbow74y"]
+  live_view: [signing_salt: "iCT0Z9EZ"]
+
+# Configures the mailer
+#
+# By default it uses the "Local" adapter which stores the emails
+# locally. You can see the emails in your browser, at "/dev/mailbox".
+#
+# For production it's recommended to configure a different adapter
+# at the `config/runtime.exs`.
+config :toy, Toy.Mailer, adapter: Swoosh.Adapters.Local
+
+# Swoosh API client is needed for adapters other than SMTP.
+config :swoosh, :api_client, false
+
+# Configure esbuild (the version is required)
+config :esbuild,
+  version: "0.12.18",
+  default: [
+    args:
+      ~w(js/app.js --bundle --target=es2016 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
+    cd: Path.expand("../assets", __DIR__),
+    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
+  ]
 
 # Configures Elixir's Logger
 config :logger, :console,

+ 9 - 11
config/dev.exs

@@ -14,20 +14,18 @@ config :toy, Toy.Repo,
 #
 # The watchers configuration can be used to run external
 # watchers to your application. For example, we use it
-# with webpack to recompile .js and .css sources.
+# with esbuild to bundle .js and .css sources.
 config :toy, ToyWeb.Endpoint,
-  http: [ip: {0, 0, 0, 0}, port: 4000],
-  debug_errors: true,
-  code_reloader: true,
+  # 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: {127, 0, 0, 1}, port: 4000],
   check_origin: false,
+  code_reloader: true,
+  debug_errors: true,
+  secret_key_base: "JckynKWcyIhf+lfqbDQGKrVyDLwhoLN3ccUerIWFDLU20PIj0Iv66q4C1n3dPat2",
   watchers: [
-    node: [
-      "node_modules/webpack/bin/webpack.js",
-      "--mode",
-      "development",
-      "--watch-stdin",
-      cd: Path.expand("../assets", __DIR__)
-    ]
+    # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
+    esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}
   ]
 
 # ## SSL Support

+ 3 - 3
config/prod.exs

@@ -22,14 +22,14 @@ config :logger, level: :info
 # to the previous section and set your `:url` port to 443:
 #
 #     config :toy, ToyWeb.Endpoint,
-#       ...
+#       ...,
 #       url: [host: "example.com", port: 443],
 #       https: [
+#         ...,
 #         port: 443,
 #         cipher_suite: :strong,
 #         keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
-#         certfile: System.get_env("SOME_APP_SSL_CERT_PATH"),
-#         transport_options: [socket_opts: [:inet6]]
+#         certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
 #       ]
 #
 # The `cipher_suite` is set to `:strong` to support only the

+ 28 - 1
config/runtime.exs

@@ -3,7 +3,7 @@ alias Config.Helper
 
 # config/runtime.exs is executed for all environments, including
 # during releases. It is executed after compilation and before the
-# system starts, so it typically used load production configuration
+# system starts, so it is typically used to load production configuration
 # and secrets from environment variables or elsewhere. Do not define
 # any compile-time configuration in here, as it won't be applied.
 # The block below contains prod specific runtime configuration.
@@ -29,6 +29,33 @@ if config_env() == :prod do
     secret_key_base: env.secret_key_base,
     server: true
 
+  # ## Using releases
+  #
+  # If you are doing OTP releases, you need to instruct Phoenix
+  # to start each relevant endpoint:
+  #
+  #     config :toy, ToyWeb.Endpoint, server: true
+  #
+  # Then you can assemble a release by calling `mix release`.
+  # See `mix help release` for more information.
+
+  # ## Configuring the mailer
+  #
+  # In production you need to configure the mailer to use a different adapter.
+  # Also, you may need to configure the Swoosh API client of your choice if you
+  # are not using SMTP. Here is an example of the configuration:
+  #
+  #     config :toy, Toy.Mailer,
+  #       adapter: Swoosh.Adapters.Mailgun,
+  #       api_key: System.get_env("MAILGUN_API_KEY"),
+  #       domain: System.get_env("MAILGUN_DOMAIN")
+  #
+  # For this example you need include a HTTP client required by Swoosh API client.
+  # Swoosh supports Hackney and Finch out of the box:
+  #
+  #     config :swoosh, :api_client, Swoosh.ApiClient.Hackney
+  #
+  # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details.
   config :toy, Toy.Repo,
     # ssl: true,
     url: env.database_url,

+ 9 - 1
config/test.exs

@@ -10,13 +10,21 @@ config :toy, Toy.Repo,
   password: "postgres",
   database: "toy_test#{System.get_env("MIX_TEST_PARTITION")}",
   hostname: "localhost",
-  pool: Ecto.Adapters.SQL.Sandbox
+  pool: Ecto.Adapters.SQL.Sandbox,
+  pool_size: 10
 
 # We don't run a server during test. If one is required,
 # you can enable the server option below.
 config :toy, ToyWeb.Endpoint,
   http: [ip: {127, 0, 0, 1}, port: 4002],
+  secret_key_base: "5gKn0HAkqLHmV6NxcPIRxhEf/tov/tfr9UuV5F50hhi90SRGy6OzA7OPhsL7IHFa",
   server: false
 
+# In test we don't send emails.
+config :toy, Toy.Mailer, adapter: Swoosh.Adapters.Test
+
 # Print only warnings and errors during test
 config :logger, level: :warn
+
+# Initialize plugs at runtime for faster test compilation
+config :phoenix, :plug_init_mode, :runtime

+ 2 - 0
lib/toy/application.ex

@@ -5,6 +5,7 @@ defmodule Toy.Application do
 
   use Application
 
+  @impl true
   def start(_type, _args) do
     children = [
       Toy.Features,
@@ -26,6 +27,7 @@ defmodule Toy.Application do
 
   # Tell Phoenix to update the endpoint configuration
   # whenever the application is updated.
+  @impl true
   def config_change(changed, _new, removed) do
     ToyWeb.Endpoint.config_change(changed, removed)
     :ok

+ 3 - 0
lib/toy/mailer.ex

@@ -0,0 +1,3 @@
+defmodule Toy.Mailer do
+  use Swoosh.Mailer, otp_app: :toy
+end

+ 1 - 1
lib/toy_web.ex

@@ -81,7 +81,7 @@ defmodule ToyWeb do
       # Use all HTML functionality (forms, tags, etc)
       use Phoenix.HTML
 
-      # Import LiveView helpers (live_render, live_component, live_patch, etc)
+      # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
       import Phoenix.LiveView.Helpers
 
       # Import basic rendering functionality (render, render_layout, etc)

+ 0 - 35
lib/toy_web/channels/user_socket.ex

@@ -1,35 +0,0 @@
-defmodule ToyWeb.UserSocket do
-  use Phoenix.Socket
-
-  ## Channels
-  # channel "room:*", ToyWeb.RoomChannel
-
-  # Socket params are passed from the client and can
-  # be used to verify and authenticate a user. After
-  # verification, you can put default assigns into
-  # the socket that will be set for all channels, ie
-  #
-  #     {:ok, assign(socket, :user_id, verified_user_id)}
-  #
-  # To deny connection, return `:error`.
-  #
-  # See `Phoenix.Token` documentation for examples in
-  # performing token verification on connect.
-  @impl true
-  def connect(_params, socket, _connect_info) do
-    {:ok, socket}
-  end
-
-  # Socket id's are topics that allow you to identify all sockets for a given user:
-  #
-  #     def id(socket), do: "user_socket:#{socket.assigns.user_id}"
-  #
-  # Would allow you to broadcast a "disconnect" event and terminate
-  # all active sockets and channels for a given user:
-  #
-  #     ToyWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
-  #
-  # Returning `nil` makes this socket anonymous.
-  @impl true
-  def id(_socket), do: nil
-end

+ 2 - 6
lib/toy_web/endpoint.ex

@@ -7,13 +7,9 @@ defmodule ToyWeb.Endpoint do
   @session_options [
     store: :cookie,
     key: "_toy_key",
-    signing_salt: "LSOaq98S"
+    signing_salt: "vFPi5mwK"
   ]
 
-  socket "/socket", ToyWeb.UserSocket,
-    websocket: true,
-    longpoll: false
-
   socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
 
   # Serve at "/" the static files from "priv/static" directory.
@@ -24,7 +20,7 @@ defmodule ToyWeb.Endpoint do
     at: "/",
     from: :toy,
     gzip: false,
-    only: ~w(css fonts images js favicon.ico robots.txt)
+    only: ~w(assets fonts images favicon.ico robots.txt)
 
   # Code reloading can be explicitly enabled under the
   # :code_reloader configuration of your endpoint.

lib/toy_web/live/page_live.html.leex → lib/toy_web/live/page_live.html.heex


+ 12 - 0
lib/toy_web/router.ex

@@ -40,4 +40,16 @@ defmodule ToyWeb.Router do
       live_dashboard "/dashboard", metrics: ToyWeb.Telemetry
     end
   end
+
+  # Enables the Swoosh mailbox preview in development.
+  #
+  # Note that preview only shows emails that were sent by the same
+  # node running the Phoenix server.
+  if Mix.env() == :dev do
+    scope "/dev" do
+      pipe_through :browser
+
+      forward "/mailbox", Plug.Swoosh.MailboxPreview
+    end
+  end
 end

+ 21 - 5
lib/toy_web/telemetry.ex

@@ -31,11 +31,27 @@ defmodule ToyWeb.Telemetry do
       ),
 
       # Database Metrics
-      summary("toy.repo.query.total_time", unit: {:native, :millisecond}),
-      summary("toy.repo.query.decode_time", unit: {:native, :millisecond}),
-      summary("toy.repo.query.query_time", unit: {:native, :millisecond}),
-      summary("toy.repo.query.queue_time", unit: {:native, :millisecond}),
-      summary("toy.repo.query.idle_time", unit: {:native, :millisecond}),
+      summary("toy.repo.query.total_time",
+        unit: {:native, :millisecond},
+        description: "The sum of the other measurements"
+      ),
+      summary("toy.repo.query.decode_time",
+        unit: {:native, :millisecond},
+        description: "The time spent decoding the data received from the database"
+      ),
+      summary("toy.repo.query.query_time",
+        unit: {:native, :millisecond},
+        description: "The time spent executing the query"
+      ),
+      summary("toy.repo.query.queue_time",
+        unit: {:native, :millisecond},
+        description: "The time spent waiting for a database connection"
+      ),
+      summary("toy.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}),

+ 1 - 1
lib/toy_web/templates/layout/app.html.eex

@@ -1,4 +1,4 @@
-<main role="main" class="container">
+<main class="container">
   <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
   <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
   <%= @inner_content %>

+ 1 - 1
lib/toy_web/templates/layout/live.html.leex

@@ -1,4 +1,4 @@
-<main role="main" class="container">
+<main class="container">
   <p class="alert alert-info" role="alert"
     phx-click="lv:clear-flash"
     phx-value-key="info"><%= live_flash(@flash, :info) %></p>

+ 2 - 2
lib/toy_web/templates/layout/root.html.leex

@@ -6,8 +6,8 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
     <%= csrf_meta_tag() %>
     <%= live_title_tag assigns[:page_title] || "Toy", suffix: " · Phoenix Framework" %>
-    <link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
-    <script defer phx-track-static type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
+    <link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")}/>
+    <script defer phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/assets/app.js")}></script>
   </head>
   <body>
     <%= @inner_content %>

+ 4 - 0
lib/toy_web/views/layout_view.ex

@@ -1,3 +1,7 @@
 defmodule ToyWeb.LayoutView do
   use ToyWeb, :view
+
+  # Phoenix LiveDashboard is available only in development by default,
+  # so we instruct Elixir to not warn if the dashboard route is missing.
+  @compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}}
 end

+ 3 - 0
lib/toy_web/views/page_view.ex

@@ -0,0 +1,3 @@
+defmodule ToyWeb.PageView do
+  use ToyWeb, :view
+end

+ 20 - 16
mix.exs

@@ -5,9 +5,9 @@ defmodule Toy.MixProject do
     [
       app: :toy,
       version: from_file(),
-      elixir: "~> 1.7",
+      elixir: "~> 1.12",
       elixirc_paths: elixirc_paths(Mix.env()),
-      compilers: [:phoenix, :gettext] ++ Mix.compilers(),
+      compilers: [:gettext] ++ Mix.compilers(),
       start_permanent: Mix.env() == :prod,
       aliases: aliases(),
       deps: deps(),
@@ -26,6 +26,7 @@ defmodule Toy.MixProject do
   end
 
   # Specifies which paths to compile per environment.
+  # "config" is required for Config.Helper module
   defp elixirc_paths(:test), do: ["lib", "config", "test/support"]
   defp elixirc_paths(_), do: ["lib", "config"]
 
@@ -34,20 +35,22 @@ defmodule Toy.MixProject do
   # Type `mix help deps` for examples and options.
   defp deps do
     [
-      {:phoenix, "~> 1.5.9"},
-      {:phoenix_ecto, "~> 4.1"},
-      {:ecto_sql, "~> 3.4"},
+      {:phoenix, "~> 1.6.2"},
+      {:phoenix_ecto, "~> 4.4"},
+      {:ecto_sql, "~> 3.6"},
       {:postgrex, ">= 0.0.0"},
-      {:phoenix_live_view, "~> 0.15.1"},
-      {:floki, ">= 0.30.0", only: :test},
-      {:phoenix_html, "~> 2.11"},
+      {:phoenix_html, "~> 3.0"},
       {:phoenix_live_reload, "~> 1.2", only: :dev},
-      {:phoenix_live_dashboard, "~> 0.4"},
-      {:telemetry_metrics, "~> 0.4"},
-      {:telemetry_poller, "~> 0.4"},
-      {:gettext, "~> 0.11"},
-      {:jason, "~> 1.0"},
-      {:plug_cowboy, "~> 2.0"}
+      {:phoenix_live_view, "~> 0.16.0"},
+      {:floki, ">= 0.30.0", only: :test},
+      {:phoenix_live_dashboard, "~> 0.5"},
+      {:esbuild, "~> 0.2", runtime: Mix.env() == :dev},
+      {:swoosh, "~> 1.3"},
+      {:telemetry_metrics, "~> 0.6"},
+      {:telemetry_poller, "~> 1.0"},
+      {:gettext, "~> 0.18"},
+      {:jason, "~> 1.2"},
+      {:plug_cowboy, "~> 2.5"}
     ]
   end
 
@@ -59,11 +62,12 @@ defmodule Toy.MixProject do
   # See the documentation for `Mix` for more info on aliases.
   defp aliases do
     [
-      setup: ["deps.get", "ecto.setup", "cmd npm install --prefix assets"],
+      setup: ["deps.get", "ecto.setup"],
       "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
       "ecto.reset": ["ecto.drop", "ecto.setup"],
       test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
-      version: ["run  -e \"Mix.Project.config[:version] |> IO.puts\""]
+      version: ["run  -e \"Mix.Project.config[:version] |> IO.puts\""],
+      "assets.deploy": ["esbuild default --minify", "phx.digest"]
     ]
   end
 

Fichier diff supprimé car celui-ci est trop grand
+ 22 - 18
mix.lock


+ 2 - 6
priv/build/build.sh

@@ -13,11 +13,7 @@ mix do local.rebar --force, \
        clean --only $MIX_ENV, \
        deps.get --only $MIX_ENV
 
-# Build static assets
-npm --prefix ./assets ci --progress=false --no-audit --loglevel=error
-npm run --prefix ./assets deploy
-
-# Run project compilation
-mix do phx.digest, \
+# Build static assets and run project compilation
+mix do assets.deploy, \
        compile --force, \
        release --overwrite

+ 21 - 6
priv/gettext/en/LC_MESSAGES/errors.po

@@ -50,13 +50,23 @@ msgid "are still associated with this entry"
 msgstr ""
 
 ## From Ecto.Changeset.validate_length/3
+msgid "should have %{count} item(s)"
+msgid_plural "should have %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+
 msgid "should be %{count} character(s)"
 msgid_plural "should be %{count} character(s)"
 msgstr[0] ""
 msgstr[1] ""
 
-msgid "should have %{count} item(s)"
-msgid_plural "should have %{count} item(s)"
+msgid "should be %{count} byte(s)"
+msgid_plural "should be %{count} byte(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should have at least %{count} item(s)"
+msgid_plural "should have at least %{count} item(s)"
 msgstr[0] ""
 msgstr[1] ""
 
@@ -65,8 +75,13 @@ msgid_plural "should be at least %{count} character(s)"
 msgstr[0] ""
 msgstr[1] ""
 
-msgid "should have at least %{count} item(s)"
-msgid_plural "should have at least %{count} item(s)"
+msgid "should be at least %{count} byte(s)"
+msgid_plural "should be at least %{count} byte(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should have at most %{count} item(s)"
+msgid_plural "should have at most %{count} item(s)"
 msgstr[0] ""
 msgstr[1] ""
 
@@ -75,8 +90,8 @@ msgid_plural "should be at most %{count} character(s)"
 msgstr[0] ""
 msgstr[1] ""
 
-msgid "should have at most %{count} item(s)"
-msgid_plural "should have at most %{count} item(s)"
+msgid "should be at most %{count} byte(s)"
+msgid_plural "should be at most %{count} byte(s)"
 msgstr[0] ""
 msgstr[1] ""
 

+ 2 - 6
test/support/channel_case.ex

@@ -29,12 +29,8 @@ defmodule ToyWeb.ChannelCase do
   end
 
   setup tags do
-    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Toy.Repo)
-
-    unless tags[:async] do
-      Ecto.Adapters.SQL.Sandbox.mode(Toy.Repo, {:shared, self()})
-    end
-
+    pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Toy.Repo, shared: not tags[:async])
+    on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
     :ok
   end
 end

+ 2 - 6
test/support/conn_case.ex

@@ -32,12 +32,8 @@ defmodule ToyWeb.ConnCase do
   end
 
   setup tags do
-    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Toy.Repo)
-
-    unless tags[:async] do
-      Ecto.Adapters.SQL.Sandbox.mode(Toy.Repo, {:shared, self()})
-    end
-
+    pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Toy.Repo, shared: not tags[:async])
+    on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
     {:ok, conn: Phoenix.ConnTest.build_conn()}
   end
 end

+ 2 - 6
test/support/data_case.ex

@@ -28,12 +28,8 @@ defmodule Toy.DataCase do
   end
 
   setup tags do
-    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Toy.Repo)
-
-    unless tags[:async] do
-      Ecto.Adapters.SQL.Sandbox.mode(Toy.Repo, {:shared, self()})
-    end
-
+    pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Toy.Repo, shared: not tags[:async])
+    on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
     :ok
   end
 

+ 8 - 0
test/toy_web/controllers/page_controller_test.exs

@@ -0,0 +1,8 @@
+defmodule ToyWeb.PageControllerTest do
+  use ToyWeb.ConnCase
+
+  test "GET /", %{conn: conn} do
+    conn = get(conn, "/")
+    assert html_response(conn, 200) =~ "Welcome to Phoenix!"
+  end
+end

+ 3 - 0
test/toy_web/views/page_view_test.exs

@@ -0,0 +1,3 @@
+defmodule ToyWeb.PageViewTest do
+  use ToyWeb.ConnCase, async: true
+end