Nous avons vu comment installer Phoenix et créer une page de base Phoenix. Nous allons maintenant réaliser une première application : la création d’un blog avec Phoenix et Liveview.
La littérature concernant cette application :
- création d’une application collaborative RichText avec liveview
- formation Productive programmer : build a medium like blog clone with phoenix
- article réalisé avec l’option html au lieu de liveview : authentication with Phoenix
- article sur la gestion des email pour l’ouverture de session
- présentation du générateur phx.gen.auth
- ajouter la notification par email
- mise en place de mailgun pour envoyer les courriels
- utilisation de Bamboo pour gérer les courriels
Vérification de l’environnement d’installation
Commençons par vérifier les outils installés sur notre poste de travail Windows :
- elixir -v ‘version d’elixir
- mix local.hex ‘mise à jour des outils hex
- mix archive.install hex phx_new ‘mise à jour de phoenix
- psql -V ‘version de postgres
C:\CarbonX1\Phoenix\Projets>elixir -v Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns] Elixir 1.15.5 (compiled with Erlang/OTP 26) C:\CarbonX1\Phoenix\Projets>mix local.hex Found existing entry: c:/Users/broussel/.mix/archives/hex-2.0.6 Are you sure you want to replace it with "https://builds.hex.pm/installs/1.14.0/hex-2.0.6.ez"? [Yn] n C:\CarbonX1\Phoenix\Projets>mix archive.install hex phx_new Resolving Hex dependencies... Resolution completed in 0.105s New: phx_new 1.7.10 * Getting phx_new (Hex package) All dependencies are up to date Compiling 11 files (.ex) Generated phx_new app Generated archive "phx_new-1.7.10.ez" with MIX_ENV=prod Found existing entry: c:/Users/broussel/.mix/archives/phx_new-1.7.10 Are you sure you want to replace it with "phx_new-1.7.10.ez"? [Yn] n C:\CarbonX1\Phoenix\Projets>psql -V psql (PostgreSQL) 15.4 C:\CarbonX1\Phoenix\Projets>
Nous avons donc les versions suivantes :
- elixir : 1.15.5 compilé avec Erlang/OTP 26
- Erlang/OTP : 26
- phoenix : 1.7.10
- postgreSQL : 15.4
Nous nous plaçons dans le dossier des projets :
- C:\CarbonX1\Phoenix\Projets
Initialisation du projet Blogset
Nous créons notre projet blogset dans le dossier Projets :
- mix phx.new blogset
C:\CarbonX1\Phoenix\Projets>mix phx.new blogset * creating blogset/lib/blogset/application.ex * creating blogset/lib/blogset.ex * creating blogset/lib/blogset_web/controllers/error_json.ex * creating blogset/lib/blogset_web/endpoint.ex * creating blogset/lib/blogset_web/router.ex * creating blogset/lib/blogset_web/telemetry.ex * creating blogset/lib/blogset_web.ex * creating blogset/mix.exs * creating blogset/README.md * creating blogset/.formatter.exs * creating blogset/.gitignore * creating blogset/test/support/conn_case.ex * creating blogset/test/test_helper.exs * creating blogset/test/blogset_web/controllers/error_json_test.exs * creating blogset/lib/blogset/repo.ex * creating blogset/priv/repo/migrations/.formatter.exs * creating blogset/priv/repo/seeds.exs * creating blogset/test/support/data_case.ex * creating blogset/lib/blogset_web/controllers/error_html.ex * creating blogset/test/blogset_web/controllers/error_html_test.exs * creating blogset/lib/blogset_web/components/core_components.ex * creating blogset/lib/blogset_web/controllers/page_controller.ex * creating blogset/lib/blogset_web/controllers/page_html.ex * creating blogset/lib/blogset_web/controllers/page_html/home.html.heex * creating blogset/test/blogset_web/controllers/page_controller_test.exs * creating blogset/lib/blogset_web/components/layouts/root.html.heex * creating blogset/lib/blogset_web/components/layouts/app.html.heex * creating blogset/lib/blogset_web/components/layouts.ex * creating blogset/priv/static/images/logo.svg * creating blogset/lib/blogset/mailer.ex * creating blogset/lib/blogset_web/gettext.ex * creating blogset/priv/gettext/en/LC_MESSAGES/errors.po * creating blogset/priv/gettext/errors.pot * creating blogset/priv/static/robots.txt * creating blogset/priv/static/favicon.ico * creating blogset/assets/js/app.js * creating blogset/assets/vendor/topbar.js * creating blogset/assets/css/app.css * creating blogset/assets/tailwind.config.js * creating blogset/assets/vendor/heroicons/LICENSE.md * creating blogset/assets/vendor/heroicons/UPGRADE.md * extracting blogset/assets/vendor/heroicons/optimized Fetch and install dependencies? [Yn] y * running mix deps.get * running mix assets.setup * running mix deps.compile We are almost there! The following steps are missing: $ cd blogset Then configure your database in config/dev.exs and run: $ mix ecto.create Start your Phoenix app with: $ mix phx.server You can also run your app inside IEx (Interactive Elixir) as: $ iex -S mix phx.server C:\CarbonX1\Phoenix\Projets>
Dans notre dossier blogset ouvrons VS Code pour voir les versions des outils qui sont définies dans mix.exs :
- cd blogset
- code .
BLOGSET/mix.exs :
# Specifies your project dependencies. # # Type `mix help deps` for examples and options. defp deps do [ {:phoenix, "~> 1.7.10"}, {:phoenix_ecto, "~> 4.4"}, {:ecto_sql, "~> 3.10"}, {:postgrex, ">= 0.0.0"}, {:phoenix_html, "~> 3.3"}, {:phoenix_live_reload, "~> 1.2", only: :dev}, {:phoenix_live_view, "~> 0.20.1"}, {:floki, ">= 0.30.0", only: :test}, {:phoenix_live_dashboard, "~> 0.8.2"}, {:esbuild, "~> 0.8", runtime: Mix.env() == :dev}, {:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev}, {:swoosh, "~> 1.3"}, {:finch, "~> 0.13"}, {:telemetry_metrics, "~> 0.6"}, {:telemetry_poller, "~> 1.0"}, {:gettext, "~> 0.20"}, {:jason, "~> 1.2"}, {:dns_cluster, "~> 0.1.1"}, {:plug_cowboy, "~> 2.5"} ] end
Nous voyons :
- ligne 6 : Phoenix > 1.7.10
- ligne 9 : Postgres > non définie
- ligne 12 : LiveView > 0.20.1
- ligne 16 ; TailWind > 0.2.0
Vérifions les accès à la base de données pour notre projet blogset dans BLOGSET/config/dev.exs :
# Configure your database config :blogset, Blogset.Repo, username: "postgres", password: "postgres", hostname: "localhost", database: "blogset_dev", stacktrace: true, show_sensitive_data_on_connection_error: true, pool_size: 10
Contrôlons que la base de données est bien configurée avec ces accès. Voir notre précédent article sur Ecto et Phoenix.
- psql -U postgres ‘pour se connecter en tant qu’utilisateur username: postgres
- \c ‘pour vérifier la connexion
- \q ‘pour quitter psql
C:\CarbonX1\Phoenix\Projets\blogset>psql -U postgres Mot de passe pour l'utilisateur postgres : psql (15.4) Attention : l'encodage console (850) diffère de l'encodage Windows (1252). Les caractères 8 bits peuvent ne pas fonctionner correctement. Voir la section « Notes aux utilisateurs de Windows » de la page référence de psql pour les détails. Saisissez « help » pour l'aide. postgres=# \c Vous êtes maintenant connecté à la base de données « postgres » en tant qu'utilisateur « postgres ». postgres=# \q C:\CarbonX1\Phoenix\Projets\blogset>
Créons la base de données pour blogset avec :
- mix ecto.create
C:\CarbonX1\Phoenix\Projets\blogset>mix ecto.create Compiling 15 files (.ex) Generated blogset app The database for Blogset.Repo has been created C:\CarbonX1\Phoenix\Projets\blogset>
Nous pouvons maintenant lancer le serveur et aller sur la page Phoenix :
- mix phx.server ‘execution du serveur
- iex -S mix phx.server ‘ou execution en mode interactif
- http://localhost:4000 ‘aller sur la page de l’application depuis un navigateur
C:\CarbonX1\Phoenix\Projets\blogset>iex -S mix phx.server [warning] Phoenix is unable to create symlinks. Phoenix' code reloader will run considerably faster if symlinks are allowed. On Windows, the lack of symlinks may even cause empty assets to be served. Luckily, you can address this issue by starting your Windows terminal at least once with "Run as Administrator" and then running your Phoenix application. [info] Running BlogsetWeb.Endpoint with cowboy 2.10.0 at 127.0.0.1:4000 (http) [info] Access BlogsetWeb.Endpoint at http://localhost:4000 [debug] Downloading esbuild from https://registry.npmjs.org/@esbuild/win32-x64/0.17.11 Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns] Interactive Elixir (1.15.5) - press Ctrl+C to exit (type h() ENTER for help) [error] driver_select(0x000001c98dddd8f0, 848, ERL_DRV_READ, 1) by tcp_inet driver #Port<0.17> stealing control of fd=848 from resource prim_tty:tty Rebuilding... Done in 423ms. [watch] build finished, watching for changes... [info] GET / [debug] Processing with BlogsetWeb.PageController.home/2 Parameters: %{} Pipelines: [:browser] [info] Sent 200 in 93ms iex(1)>
La page de notre projet dans Chrome :
La version Phoenix utilisée est affichée en haut à gauche de la page : Phoenix Framework v.1.7.10.
Création de l’authentification du blog avec Phoenix
Phoenix permet de générer la partie authentification des applications. Nous avons :
- Accounts : le module de gestion des Comptes utilisateurs
- User : le module pour gérer les utilisateurs
- users : la table des utilisateurs dans la base de données
La génération de la partie authentification du projet blogset se fait en étant dans le dossier du projet avec la commande suivante :
- cd C:\CarbonX1\Phoenix\Projets\blogset
- mix phx.gen.auth Accounts User users
Et nous choisissons de créer le système d’authentification avec LiveView (version par defaut).
C:\CarbonX1\Phoenix\Projets\blogset>mix phx.gen.auth Accounts User users An authentication system can be created in two different ways: - Using Phoenix.LiveView (default) - Using Phoenix.Controller only Do you want to create a LiveView based authentication system? [Yn] Y * creating priv/repo/migrations/20231130151218_create_users_auth_tables.exs * creating lib/blogset/accounts/user_notifier.ex * creating lib/blogset/accounts/user.ex * creating lib/blogset/accounts/user_token.ex * creating lib/blogset_web/user_auth.ex * creating test/blogset_web/user_auth_test.exs * creating lib/blogset_web/controllers/user_session_controller.ex * creating test/blogset_web/controllers/user_session_controller_test.exs * creating lib/blogset_web/live/user_registration_live.ex * creating test/blogset_web/live/user_registration_live_test.exs * creating lib/blogset_web/live/user_login_live.ex * creating test/blogset_web/live/user_login_live_test.exs * creating lib/blogset_web/live/user_reset_password_live.ex * creating test/blogset_web/live/user_reset_password_live_test.exs * creating lib/blogset_web/live/user_forgot_password_live.ex * creating test/blogset_web/live/user_forgot_password_live_test.exs * creating lib/blogset_web/live/user_settings_live.ex * creating test/blogset_web/live/user_settings_live_test.exs * creating lib/blogset_web/live/user_confirmation_live.ex * creating test/blogset_web/live/user_confirmation_live_test.exs * creating lib/blogset_web/live/user_confirmation_instructions_live.ex * creating test/blogset_web/live/user_confirmation_instructions_live_test.exs * creating lib/blogset/accounts.ex * injecting lib/blogset/accounts.ex * creating test/blogset/accounts_test.exs * injecting test/blogset/accounts_test.exs * creating test/support/fixtures/accounts_fixtures.ex * injecting test/support/fixtures/accounts_fixtures.ex * injecting test/support/conn_case.ex * injecting config/test.exs * injecting mix.exs * injecting lib/blogset_web/router.ex * injecting lib/blogset_web/router.ex - imports * injecting lib/blogset_web/router.ex - plug * injecting lib/blogset_web/components/layouts/root.html.heex Please re-fetch your dependencies with the following command: $ mix deps.get Remember to update your repository by running migrations: $ mix ecto.migrate Once you are ready, visit "/users/register" to create your account and then access "/dev/mailbox" to see the account confirmation email. C:\CarbonX1\Phoenix\Projets\blogset>
une fois la génération effectuée, nous devons charger les nouvelles dépendances et exécuter la migration de la base de données :
- mix deps.get
C:\CarbonX1\Phoenix\Projets\blogset>mix deps.get Resolving Hex dependencies... Resolution completed in 0.273s New: comeonin 5.4.0 pbkdf2_elixir 2.2.0 Unchanged: castore 1.0.4 cowboy 2.10.0 cowboy_telemetry 0.4.0 cowlib 2.12.1 db_connection 2.6.0 decimal 2.1.1 dns_cluster 0.1.1 ecto 3.11.0 ecto_sql 3.11.0 esbuild 0.8.1 expo 0.4.1 file_system 0.2.10 finch 0.16.0 floki 0.35.2 gettext 0.23.1 hpax 0.1.2 jason 1.4.1 mime 2.0.5 mint 1.5.1 nimble_options 1.0.2 nimble_pool 1.0.0 phoenix 1.7.10 phoenix_ecto 4.4.3 phoenix_html 3.3.3 phoenix_live_dashboard 0.8.3 phoenix_live_reload 1.4.1 phoenix_live_view 0.20.1 phoenix_pubsub 2.1.3 phoenix_template 1.0.3 plug 1.15.2 plug_cowboy 2.6.1 plug_crypto 2.0.0 postgrex 0.17.3 ranch 1.8.0 swoosh 1.14.1 tailwind 0.2.2 telemetry 1.2.1 telemetry_metrics 0.6.1 telemetry_poller 1.0.0 websock 0.5.3 websock_adapter 0.5.5 * Getting pbkdf2_elixir (Hex package) * Getting comeonin (Hex package) C:\CarbonX1\Phoenix\Projets\blogset>
- mix ecto.migrate
C:\CarbonX1\Phoenix\Projets\blogset>mix ecto.migrate ==> comeonin Compiling 3 files (.ex) Generated comeonin app ==> pbkdf2_elixir Compiling 5 files (.ex) Generated pbkdf2_elixir app ==> blogset Compiling 15 files (.ex) Generated blogset app 16:15:59.036 [info] == Running 20231130151218 Blogset.Repo.Migrations.CreateUsersAuthTables.change/0 forward 16:15:59.038 [info] execute "CREATE EXTENSION IF NOT EXISTS citext" 16:15:59.088 [info] create table users 16:15:59.105 [info] create index users_email_index 16:15:59.108 [info] create table users_tokens 16:15:59.117 [info] create index users_tokens_user_id_index 16:15:59.119 [info] create index users_tokens_context_token_index 16:15:59.121 [info] == Migrated 20231130151218 in 0.0s C:\CarbonX1\Phoenix\Projets\blogset>
Visualisation des pages créées par le module d’authentification
Nous rechargeons la page de l’application pour voir les changement généré par la création du module d’authentification :
- iex -S mix phx.server
- http://localhost:4000
Nous avons 2 boutons qui apparaissent en haut à droite de la page, Register et Log in (1) :
Nous pouvons créer un compte puis nous connecter et nous déconnecter de l’application. Notre email de connexion apparait sur la droite, confirmant que nous sommes bien identifié par l’application.
Lorsque nous activons la création du compte, Liveview affiche un message de succès :
L’adresse courriel est affichée en haut de la page en signe de connection :
Lors de la déconnexion par logout, un message liveview confirme la déconnexion :
La page d’identification offre bien un bouton Mot de passe perdu :
Une fois identifié, nous avons le message « Welcom back! » de Liveview :
Lorsqu’on est connecté, nous avons la possibilité de voir les informations de notre compte par settings :
Lorsque nous souhaitons nous identifier et que nous avons perdu notre mot de passe nous arrivons sur la page suivante :
Liveview affiche un message pour la prise en compte du message perdu :
Vérification de la base de données utilisée par le module authentification
Nous n’avons pas encore mis en place la gestion des courriels permettant de vérifier cette fonctionnalité. Ce qui sera le sujet d’un prochain article : la gestion des boites courriel en local dans le projet Phoenix.
Nous pouvons vérifier dans la base de données la création du compte de l’utilisateur dans notre application blogset.
- psql -U postgres ‘ouvrir psql avec l’utilisateur postgres
- \c blogset_dev ‘se connecter à la base blogset_dev
- \d ‘afficher la liste des tables de la base
- select * from users; ‘afficher la table users
- \q ‘pour quitter psql
C:\Users\broussel>psql -U postgres Mot de passe pour l'utilisateur postgres : psql (15.4) Attention : l'encodage console (850) diffère de l'encodage Windows (1252). Les caractères 8 bits peuvent ne pas fonctionner correctement. Voir la section « Notes aux utilisateurs de Windows » de la page référence de psql pour les détails. Saisissez « help » pour l'aide. postgres=# \c blogset_dev Vous êtes maintenant connecté à la base de données « blogset_dev » en tant qu'utilisateur « postgres ». blogset_dev=# \d Liste des relations SchÚma | Nom | Type | PropriÚtaire --------+---------------------+----------+-------------- public | schema_migrations | table | postgres public | users | table | postgres public | users_id_seq | sÚquence | postgres public | users_tokens | table | postgres public | users_tokens_id_seq | sÚquence | postgres (5 lignes) blogset_dev=# select * from users; id | email | hashed_password | confirmed_at | inserted_at | updated_at ----+----------------+-------------------------------------------------------------------------------------------------------------------------------------+--------------+---------------------+--------------------- 1 | test@gmail.com | $pbkdf2-sha512$160000$oo0y35jAReYWGQaoEfkekw$.71FgZd.PQ0r8g52M6SVIldPT7DXF5vO6G2bdYW/3Zix9RleOrWGrkONvXgqTQjO.UURPH8J6vI.F/TroVrW.Q | | 2023-11-30 15:30:36 | 2023-11-30 15:30:36 (1 ligne) blogset_dev=# \q C:\Users\broussel>
Nous voyons que la table users contient l’email (ligne 2), un identifiant unique id (créé par defaut sans à avoir à être indiqué dans le schema) et une colonne hashed_password (ligne 4). Il n’y a pas de password dans la table, car le scema créé a indique que le password est virtuel (ligne 3). Le générateur a mis en place une gestion sécurisée des mots de passe et de l’authentification en ne conservant pas les mots de passe.
- cd C:\CarbonX1\Phoenix\Projets\blogset ‘aller sur le repertoire du projet
- code . ‘ouvri VS Code
Nous regardons la définition du schema créé pour la table users :
- BLOGSET/lib/blogset/accounts/user.ex
schema "users" do field :email, :string field :password, :string, virtual: true, redact: true field :hashed_password, :string, redact: true field :confirmed_at, :naive_datetime timestamps(type: :utc_datetime) end
La création de la table users passe par les migrations. Ecto réalise les modifications de la base de données à partir de migration. Une migration est un fichier décrivant les changements à effectuer dans la base de données. Pour la création de la table User, nous avons le fichier :
- BLOGSET/priv/repo/migration/20231130151218_create_users_auth_tables.exs
defmodule Blogset.Repo.Migrations.CreateUsersAuthTables do use Ecto.Migration def change do execute "CREATE EXTENSION IF NOT EXISTS citext", "" create table(:users) do add :email, :citext, null: false add :hashed_password, :string, null: false add :confirmed_at, :naive_datetime timestamps(type: :utc_datetime) end create unique_index(:users, [:email]) create table(:users_tokens) do add :user_id, references(:users, on_delete: :delete_all), null: false add :token, :binary, null: false add :context, :string, null: false add :sent_to, :string timestamps(updated_at: false) end create index(:users_tokens, [:user_id]) create unique_index(:users_tokens, [:context, :token]) end end
Le fichier est executé une seule fois.
phx.gen.auth a aussi créé des fonctions de test que nous pouvons exécuter avec mix test.
- mix test
C:\CarbonX1\Phoenix\Projets\blogset>mix test ==> floki Compiling 1 file (.xrl) Compiling 2 files (.erl) Compiling 29 files (.ex) Generated floki app ==> decimal Compiling 4 files (.ex) Generated decimal app ==> mime Compiling 1 file (.ex) Generated mime app ==> nimble_options Compiling 3 files (.ex) Generated nimble_options app ===> Analyzing applications... ===> Compiling telemetry ==> telemetry_metrics Compiling 7 files (.ex) Generated telemetry_metrics app ===> Analyzing applications... ===> Compiling telemetry_poller ==> jason Compiling 10 files (.ex) Generated jason app ==> comeonin Compiling 3 files (.ex) Generated comeonin app ==> pbkdf2_elixir Compiling 5 files (.ex) Generated pbkdf2_elixir app ==> db_connection Compiling 15 files (.ex) Generated db_connection app ==> expo Compiling 2 files (.erl) Compiling 21 files (.ex) Generated expo app ==> phoenix_pubsub Compiling 11 files (.ex) Generated phoenix_pubsub app ==> plug_crypto Compiling 5 files (.ex) Generated plug_crypto app ==> hpax Compiling 4 files (.ex) Generated hpax app ==> dns_cluster Compiling 1 file (.ex) Generated dns_cluster app ==> gettext Compiling 17 files (.ex) Generated gettext app ===> Analyzing applications... ===> Compiling ranch ==> ecto Compiling 56 files (.ex) Generated ecto app ==> plug Compiling 1 file (.erl) Compiling 40 files (.ex) Generated plug app ==> phoenix_html Compiling 9 files (.ex) Generated phoenix_html app ==> phoenix_template Compiling 4 files (.ex) Generated phoenix_template app ==> postgrex Compiling 68 files (.ex) Generated postgrex app ==> ecto_sql Compiling 25 files (.ex) Generated ecto_sql app ==> nimble_pool Compiling 2 files (.ex) Generated nimble_pool app ==> castore Compiling 1 file (.ex) Generated castore app ==> esbuild Compiling 4 files (.ex) Generated esbuild app ==> tailwind Compiling 3 files (.ex) warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead lib/tailwind.ex:72: Tailwind.start/2 warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead lib/tailwind.ex:86: Tailwind.start/2 Generated tailwind app ==> mint Compiling 1 file (.erl) Compiling 19 files (.ex) Generated mint app ==> finch Compiling 13 files (.ex) warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead lib/finch/http2/pool.ex:362: Finch.HTTP2.Pool.connected/3 warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead lib/finch/http2/pool.ex:460: Finch.HTTP2.Pool.connected_read_only/3 Generated finch app ==> websock Compiling 1 file (.ex) Generated websock app ===> Analyzing applications... ===> Compiling cowlib ===> Analyzing applications... ===> Compiling cowboy ===> Analyzing applications... ===> Compiling cowboy_telemetry ==> plug_cowboy Compiling 5 files (.ex) warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead lib/plug/cowboy.ex:352: Plug.Cowboy.to_args/5 Generated plug_cowboy app ==> swoosh Compiling 48 files (.ex) Generated swoosh app ==> websock_adapter Compiling 4 files (.ex) Generated websock_adapter app ==> phoenix Compiling 71 files (.ex) Generated phoenix app ==> phoenix_live_view Compiling 39 files (.ex) Generated phoenix_live_view app ==> phoenix_live_dashboard Compiling 36 files (.ex) Generated phoenix_live_dashboard app ==> phoenix_ecto Compiling 7 files (.ex) Generated phoenix_ecto app ==> blogset Compiling 31 files (.ex) Generated blogset app ................................................................................................................................ Finished in 1.0 seconds (0.7s async, 0.3s sync) 128 tests, 0 failures Randomized with seed 269811 C:\CarbonX1\Phoenix\Projets\blogset>
Découverte du code généré par mix phx.gen.auth
Dans la partie router.ex, nous avons 2 zones pour placer nos routes vers les pages :
- la zone libre
- la zone sécurisée
BLOGSET/lib/blogset_web/router.ex :
defmodule BlogsetWeb.Router do use BlogsetWeb, :router import BlogsetWeb.UserAuth pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_live_flash plug :put_root_layout, html: {BlogsetWeb.Layouts, :root} plug :protect_from_forgery plug :put_secure_browser_headers plug :fetch_current_user end pipeline :api do plug :accepts, ["json"] end scope "/", BlogsetWeb do pipe_through :browser get "/", PageController, :home end # Other scopes may use custom stacks. # scope "/api", BlogsetWeb do # pipe_through :api # end # Enable LiveDashboard and Swoosh mailbox preview in development if Application.compile_env(:blogset, :dev_routes) do # If you want to use the LiveDashboard in production, you should put # it behind authentication and allow only admins to access it. # If your application does not have an admins-only section yet, # you can use Plug.BasicAuth to set up some basic authentication # as long as you are also using SSL (which you should anyway). import Phoenix.LiveDashboard.Router scope "/dev" do pipe_through :browser live_dashboard "/dashboard", metrics: BlogsetWeb.Telemetry forward "/mailbox", Plug.Swoosh.MailboxPreview end end ## Authentication routes scope "/", BlogsetWeb do pipe_through [:browser, :redirect_if_user_is_authenticated] live_session :redirect_if_user_is_authenticated, on_mount: [{BlogsetWeb.UserAuth, :redirect_if_user_is_authenticated}] do live "/users/register", UserRegistrationLive, :new live "/users/log_in", UserLoginLive, :new live "/users/reset_password", UserForgotPasswordLive, :new live "/users/reset_password/:token", UserResetPasswordLive, :edit end post "/users/log_in", UserSessionController, :create end scope "/", BlogsetWeb do pipe_through [:browser, :require_authenticated_user] live_session :require_authenticated_user, on_mount: [{BlogsetWeb.UserAuth, :ensure_authenticated}] do live "/users/settings", UserSettingsLive, :edit live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email end end scope "/", BlogsetWeb do pipe_through [:browser] delete "/users/log_out", UserSessionController, :delete live_session :current_user, on_mount: [{BlogsetWeb.UserAuth, :mount_current_user}] do live "/users/confirm/:token", UserConfirmationLive, :edit live "/users/confirm", UserConfirmationInstructionsLive, :new end end end
Si nous plaçons une route en zone sécurisé require_authenticated_user, lorsque l’utilisateur saisie l’url sans être authentifié, Phoenix demande à l’utilisateur de s’identifier.
Nous pouvons vérifier la définition de require_authenticated_user en recherchant dans VS Code :
La recherche dans dans VS Code est actrivée par la loupe (1). Il nous suffit de copier le texte recherche (2) dans la boite de recherche (3) pour trouver dans le code la définition de require_authenticated_user. En cliquant sur le résultat, la fenetre avec lea définition s’ouvre :
BLOGSET/lib/blogset_web/user_auth.ex :
@doc """ Used for routes that require the user to be authenticated. If you want to enforce the user email is confirmed before they use the application at all, here would be a good place. """ def require_authenticated_user(conn, _opts) do if conn.assigns[:current_user] do conn else conn |> put_flash(:error, "You must log in to access this page.") |> maybe_store_return_to() |> redirect(to: ~p"/users/log_in") |> halt() end end
Complément à ajouter à l’authentification
Pour avoir les fonctionalités usuelles d’un site internet, nous devons ajouter deux fonctionnalités complémentaires :
- la vérification de l’existance réelle du courriel indiqué à l’ouverture du compte
- la gestion de la perte de mot de passe
Pour une gestion des comptes efficace, nous avons donc besoin d’un envoie de courriel à l’utilisateur. Cette fonction est essentielle pour finaliser le module d’authentification de nos applications.
Nous regarderons dans un autre article comment mettre cela en place.
Création des articles du blog avec Phoenix
Nous avons besoin de pouvoir créer des articles associés à notre nom d’auteur authentifié dans blogset.
Pour créer les Articles, nous créons un Contexte Stories, un module Story et la table stories. Cela se fait par la commande :
- mix phx.gen.live Stories Story stories
Nous devons aussi ajouter les champs de la table pour la base de données avec leur type :
- Title : string
- Body : text
- User_id : référence à users
La commande complète est la suivante :
- cd C:\CarbonX1\Phoenix\Projets\blogset
- mix phx.gen.live Stories Story stories title:string, body:text, user_id:references:users
C:\CarbonX1\Phoenix\Projets\blogset>mix phx.gen.live Stories Story stories title:string, body:text, user_id:references:users * creating lib/blogset_web/live/story_live/show.ex * creating lib/blogset_web/live/story_live/index.ex * creating lib/blogset_web/live/story_live/form_component.ex * creating lib/blogset_web/live/story_live/index.html.heex * creating lib/blogset_web/live/story_live/show.html.heex * creating test/blogset_web/live/story_live_test.exs * creating lib/blogset/stories/story.ex * creating priv/repo/migrations/20231201102833_create_stories.exs * creating lib/blogset/stories.ex * injecting lib/blogset/stories.ex * creating test/blogset/stories_test.exs * injecting test/blogset/stories_test.exs * creating test/support/fixtures/stories_fixtures.ex * injecting test/support/fixtures/stories_fixtures.ex Add the live routes to your browser scope in lib/blogset_web/router.ex: live "/stories", StoryLive.Index, :index live "/stories/new", StoryLive.Index, :new live "/stories/:id/edit", StoryLive.Index, :edit live "/stories/:id", StoryLive.Show, :show live "/stories/:id/show/edit", StoryLive.Show, :edit Remember to update your repository by running migrations: $ mix ecto.migrate C:\CarbonX1\Phoenix\Projets\blogset>
Nous devons executer mix ecto.migrate pour générer la table dans PostgreSQL.
C:\CarbonX1\Phoenix\Projets\blogset>mix ecto.migrate Compiling 5 files (.ex) warning: no route path for BlogsetWeb.Router matches "/stories" lib/blogset_web/live/story_live/index.html.heex:40: BlogsetWeb.StoryLive.Index.render/1 warning: no route path for BlogsetWeb.Router matches "/stories/#{assigns.story}" lib/blogset_web/live/story_live/show.html.heex:25: BlogsetWeb.StoryLive.Show.render/1 warning: no route path for BlogsetWeb.Router matches "/stories/#{assigns.story}" lib/blogset_web/live/story_live/show.html.heex:18: BlogsetWeb.StoryLive.Show.render/1 warning: no route path for BlogsetWeb.Router matches "/stories" lib/blogset_web/live/story_live/index.html.heex:33: BlogsetWeb.StoryLive.Index.render/1 warning: no route path for BlogsetWeb.Router matches "/stories" lib/blogset_web/live/story_live/show.html.heex:16: BlogsetWeb.StoryLive.Show.render/1 warning: no route path for BlogsetWeb.Router matches "/stories/#{story}/edit" lib/blogset_web/live/story_live/index.html.heex:21: BlogsetWeb.StoryLive.Index.render/1 warning: no route path for BlogsetWeb.Router matches "/stories/#{assigns.story}/show/edit" lib/blogset_web/live/story_live/show.html.heex:5: BlogsetWeb.StoryLive.Show.render/1 warning: no route path for BlogsetWeb.Router matches "/stories/#{story}" lib/blogset_web/live/story_live/index.html.heex:19: BlogsetWeb.StoryLive.Index.render/1 warning: no route path for BlogsetWeb.Router matches "/stories/#{story}" lib/blogset_web/live/story_live/index.html.heex:13: BlogsetWeb.StoryLive.Index.render/1 warning: no route path for BlogsetWeb.Router matches "/stories/new" lib/blogset_web/live/story_live/index.html.heex:4: BlogsetWeb.StoryLive.Index.render/1 Generated blogset app 11:34:02.859 [info] == Running 20231201102833 Blogset.Repo.Migrations.CreateStories.change/0 forward 11:34:02.861 [info] create table stories 11:34:02.877 [info] create index stories_user_id_index 11:34:02.888 [info] == Migrated 20231201102833 in 0.0s C:\CarbonX1\Phoenix\Projets\blogset>
Nous avons les messages d’alerte nous indiquant que nous devons mettre à jour le fichier router.ex pour pouvoir accéder aux pages créées pour les Stories.
Rendre les pages du blog accessible avec les routes
Ajoutons les routes dans le router.ex :
BLOGSET/lib/blogset_web/router.ex :
defmodule BlogsetWeb.Router do use BlogsetWeb, :router import BlogsetWeb.UserAuth pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_live_flash plug :put_root_layout, html: {BlogsetWeb.Layouts, :root} plug :protect_from_forgery plug :put_secure_browser_headers plug :fetch_current_user end pipeline :api do plug :accepts, ["json"] end scope "/", BlogsetWeb do pipe_through :browser get "/", PageController, :home end
Au début du fichier, nous avons ligne 20, scope « / », BlogsetWeb do.
Nous ajoutons juste après get « / », PageController, :home, en ligne 24 et suivante les routes indiquées lors de la génération mix phx.gen.live, (lignes 19 à 24).
scope "/", BlogsetWeb do pipe_through :browser get "/", PageController, :home live "/stories", StoryLive.Index, :index live "/stories/new", StoryLive.Index, :new live "/stories/:id/edit", StoryLive.Index, :edit live "/stories/:id", StoryLive.Show, :show live "/stories/:id/show/edit", StoryLive.Show, :edit end
Nous pouvons maintenant executer les test pour vérifier si tout fonctionne :
- mix test
C:\CarbonX1\Phoenix\Projets\blogset>mix test Compiling 7 files (.ex) Generated blogset app .............................................................................................................................................. Finished in 1.9 seconds (0.7s async, 1.1s sync) 142 tests, 0 failures Randomized with seed 347348 C:\CarbonX1\Phoenix\Projets\blogset>
Visualisation des pages du blog créé avec Phoenix
Allons voir comment l’application se présente. Nous devrons indiquer les url des pages à voir :
- cd C:\CarbonX1\Phoenix\Projets\blogset ‘se placer dans le dossier du projet
- iex -S mix phx.server ‘activer le serveur avec le projet blogset
- http://localhost:4000/stories/ ‘aller sur la page montrant les articles
Comme nous n’avons pas encore d’article, la page est vide :
La page présente bien les boutons pour permettre la connexion, et la création de compte. Nous avons aussi la possibilité de créer un article avec New Story.
Nous créons un article et revenons dans la liste des articles :
Au regard de l’article dans la liste nous avons deux boutons, l’un pour supprimer l’article, l’autre pour le modifier. Editons l’article :
Et la mise à jour se retrouve dans la liste :
On notera les messages explicites dans des bulles vertes, proposés par liveview.
Nous pouvons voir les articles dans la base de données :
- cd C:\CarbonX1\Phoenix\Projets\blogset ‘se placer dans le dossier du projet
- psql -U postgres ‘pour se connecter en tant qu’utilisateur username: postgres
- \c ‘pour vérifier la connexion
- \c blogset_dev ‘ pour se connecter à la base blogset_dev
- \d ‘ liste les tables de la base blogset_dev
- \d stories ‘ liste les colonnes de la table stories
- select * from stories ; ‘liste les données de la table stories
- \q ‘pour quitter postgres
C:\CarbonX1\Phoenix\Projets\blogset>psql -U postgres Mot de passe pour l'utilisateur postgres : psql (15.4) Attention : l'encodage console (850) diffère de l'encodage Windows (1252). Les caractères 8 bits peuvent ne pas fonctionner correctement. Voir la section « Notes aux utilisateurs de Windows » de la page référence de psql pour les détails. Saisissez « help » pour l'aide. postgres=# \c Vous êtes maintenant connecté à la base de données « postgres » en tant qu'utilisateur « postgres ». postgres=# \c blogset_dev Vous êtes maintenant connecté à la base de données « blogset_dev » en tant qu'utilisateur « postgres ». blogset_dev=# \d Liste des relations SchÚma | Nom | Type | PropriÚtaire --------+---------------------+----------+-------------- public | schema_migrations | table | postgres public | stories | table | postgres public | stories_id_seq | sÚquence | postgres public | users | table | postgres public | users_id_seq | sÚquence | postgres public | users_tokens | table | postgres public | users_tokens_id_seq | sÚquence | postgres (7 lignes) blogset_dev=# \d stories Table ½ public.stories ╗ Colonne | Type | Collationnement | NULL-able | Par dÚfaut -------------+--------------------------------+-----------------+-----------+------------------------------------- id | bigint | | not null | nextval('stories_id_seq'::regclass) title | character varying(255) | | | body | text | | | user_id | bigint | | | inserted_at | timestamp(0) without time zone | | not null | updated_at | timestamp(0) without time zone | | not null | Index : "stories_pkey" PRIMARY KEY, btree (id) "stories_user_id_index" btree (user_id) Contraintes de clÚs ÚtrangÞres : "stories_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) blogset_dev=# select * from stories ; id | title | body | user_id | inserted_at | updated_at ----+------------------+----------------------+---------+---------------------+--------------------- 2 | Article numÚro 2 | mon deuxiÞme article | | 2023-12-01 14:36:25 | 2023-12-01 14:36:25 (1 ligne) blogset_dev=# \q C:\CarbonX1\Phoenix\Projets\blogset>
Les données sont bien enregistrées dans la base de données.
Conclusion
Nous avons réalisé une première étape dans la création d’un blog avec Phoenix. Nous avons créé le schéma de la base de données et les vues avec liveview. Les enchainements sont fonctionnels pour :
- les utilisateurs du blog
- les articles du blog
Nous devons améliorer notre blog en créant un lien entre les articles et les auteurs. Nous devons aussi utiliser la puissance de l’autorisation Phoenix en ajoutant les messages par courriel, et nous devons mettre en place l’authentification des utilisateurs afin de données des droits sur les articles pour la suppression et la modification des articles.
Ce sera l’objet de notre prochaine article : sécuriser un blog avec Phoenix.