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
xxxxxxxxxx
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.