Création d’un blog avec Phoenix

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 :

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 :

Créer un blog avec Phoenix#1-Visualisation de la page html du projet
Créer un blog avec Phoenix#1-Visualisation de la page html du projet

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) :

Créer un blog avec Phoenix#2-La page accueil avec authentification
Créer un blog avec Phoenix#2-La page accueil avec authentification

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.

Créer un blog avec Phoenix#3-La page création de compte
Créer un blog avec Phoenix#3-La page création de compte

Lorsque nous activons la création du compte, Liveview affiche un message de succès :

Créer un blog avec Phoenix#4-La confirmation liveview pour la création du compte
Créer un blog avec Phoenix#4-La confirmation liveview pour la création du compte

L’adresse courriel est affiché en haut de la page en signe de connection :

Créer un blog avec Phoenix#5-Adresse courriel du compte affichée
Créer un blog avec Phoenix#5-Adresse courriel du compte affichée

Lors de la déconnexion par logout, un message liveview confirme la déconnexion :

Créer un blog avec Phoenix#6-Message liveview de déconnexion
Créer un blog avec Phoenix#6-Message liveview de déconnexion

La page d’identification offre bien un bouton Mot de passe perdu :

Créer un blog avec Phoenix#7-La page d'identification
Créer un blog avec Phoenix#7-La page d’identification

Une fois identifié, nous avons le message « Welcom back! » de Liveview :

Créer un blog avec Phoenix#8-Après identification
Créer un blog avec Phoenix#8-Après identification

Lorsqu’on est connecté, nous avons la possibilité de voir les informations de notre compte par settings :

Créer un blog avec Phoenix#9-Propriété du compte identifié
Créer un blog avec Phoenix#9-Propriété du compte identifié

Lorsque nous souhaitons nous identifier et que nous avons perdu notre mot de passe nous arrivons sur la page suivante :

Créer un blog avec Phoenix#10-Page pour le mot de passe perdu
Créer un blog avec Phoenix#10-Page pour le mot de passe perdu

Liveview affiche un message pour la prise en compte du message perdu :

Créer un blog avec Phoenix#11-Prise en compte de la demande de changement du mot de passe
Créer un blog avec Phoenix#11-Prise en compte de la demande de changement du mot de passe

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 :

Créer un blog avec Phoenix#12-Utilisation de la recherche dans VS Code
Créer un blog avec Phoenix#12-Utilisation de la recherche 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 :

Créer un blog avec Phoenix#13-La page avec la liste des articles
Créer un blog avec Phoenix#13-La page avec la liste des articles

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.

Créer un blog avec Phoenix#14-La page pour créer les articles
Créer un blog avec Phoenix#14-La page pour créer les articles

Nous créons un article et revenons dans la liste des articles :

Créer un blog avec Phoenix#15-Le premier article de la liste
Créer un blog avec Phoenix#15-Le premier article de la liste

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 :

Créer un blog avec Phoenix#16-l'article en modification
Créer un blog avec Phoenix#16-l’article en modification

Et la mise à jour se retrouve dans la liste :

Créer un blog avec Phoenix#17-liste avec article modifié
Créer un blog avec Phoenix#17-liste avec article modifié

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.

Si vous avez aimé l'article vous êtes libre de le partager :-)

Laisser un commentaire