Une page d’accueil pour le blog Phoenix

Nous avons montré comment créer et sécuriser un blog créé avec Phoenix. Poursuivons la réalisation de ce blog en ajoutant la possibilité pour les visiteurs de voir tous les articles du blog Phoenix. Nous allons créer la page d’accueil pour le blog Phoenix.

Cet article est la continuation de la lecture de Productive programmer : build a medium like blog clone with phoenix.

Cet article est la suite des deux premières parties :

Etat des lieux du blog créé avec Phoenix

Commençons par regarder où nous en sommes de notre blog.

Quel environnement est installé sur notre poste Windows

Nous réalisons le projet sur Windows. Le dossier utilisé est celui du projet blogset. Commençons par nous placer dans ce dossier, puis vérifions les outils installés :

  • cd C:\CarbonX1\Phoenix\Projets\blogset
  • code .

Quelles sont les versions disponibles :

  • 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\blogset>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\blogset>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\blogset>mix archive.install hex phx_new
Resolving Hex dependencies...
Resolution completed in 0.109s
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\blogset>psql -V
psql (PostgreSQL) 16.1

C:\CarbonX1\Phoenix\Projets\blogset>

Regardons le contenu de la base de données :

  • psql -U postgres ‘connexion à la base de données
  • \c ‘ vérification de la connexion
  • \l ‘liste des bases de données projet enregistrées
  • \c blogset_dev ‘connexion à la base blogset_dev
  • \d ‘liste des tables créées dans blogset_dev
  • select * from users ; ‘liste des utillisateurs enregistrés dans la table users
  • \q ‘quitter postgreSQL
C:\CarbonX1\Phoenix\Projets\blogset>psql -U postgres
Mot de passe pour l'utilisateur postgres :
psql (16.1)
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=# \l
                                                                        Liste des bases de donnÚes
           Nom            | PropriÚtaire | Encodage | Fournisseur de locale |  Collationnement   |    Type caract.    | Locale ICU | RÞgles ICU : |    Droits d'accÞs
--------------------------+--------------+----------+-----------------------+--------------------+--------------------+------------+--------------+-----------------------
 blogset_dev              | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              |
 blogset_test             | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              |
 postgres                 | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              |
 template0                | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              | =c/postgres          +
                          |              |          |                       |                    |                    |            |              | postgres=CTc/postgres
 template1                | postgres     | UTF8     | libc                  | French_France.1252 | French_France.1252 |            |              | =c/postgres          +
                          |              |          |                       |                    |                    |            |              | postgres=CTc/postgres
(7 lignes)


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$LaCTuJXeTbcqs1.nOD9fVg$VNN..GEIbciqJvFfzgeKMAoi/7sSDEcvlNqWvV9SLia.754SznImYjVSNx.Nmm5OB/9XMjS/2n5mpPBQLunTSw | 2023-12-15 15:36:11 | 2023-12-15 15:26:33 | 2023-12-15 15:36:11
(1 ligne)


blogset_dev=# \q

C:\CarbonX1\Phoenix\Projets\blogset>cd C:\CarbonX1\Phoenix\Projets\blogset

Nous voyons que nous avons bien la table users utilisé par l’authothentification, mais pas la table stories contenant les articles.

Remarque : nous avons fait une mise à jour de la base de données PostgreSQL vers la dernière version

Nous regardons comment se présente la table schema_migration :

  • psql -U postgres ‘connexion à la base de données
  • \l ‘liste des bases de données projet enregistrées
  • \c blogset_dev ‘connexion à la base blogset_dev
  • \d ‘liste des tables créées dans blogset_dev
  • \q ‘quitter postgreSQL
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 schema_migrations ;
    version     |     inserted_at
----------------+---------------------
 20231215151534 | 2023-12-15 15:17:57
(1 ligne)


blogset_dev=# \q

C:\CarbonX1\Phoenix\Projets\blogset>

Si nous regardons dans VS Code la liste des migrations nous avons :

  • 20231130151218_create_users_auth_tables.exs
  • 20231201102833_create_stories.exs

Regardons comment mettre à jour notre base de données et comprendre comment fonctionne la migration.

Nous allons vérifier à la fois les dépendances et éxecuter les migrations :

  • mix deps.get
C:\CarbonX1\Phoenix\Projets\blogset>mix deps.get
Resolving Hex dependencies...
Resolution completed in 0.26s
Unchanged:
  castore 1.0.4
  comeonin 5.4.0
  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
  pbkdf2_elixir 2.2.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
All dependencies are up to date

C:\CarbonX1\Phoenix\Projets\blogset>
  • mix ecto.migrate
C:\CarbonX1\Phoenix\Projets\blogset>mix ecto.migrate
** (UndefinedFunctionError) function Ecto.Adapters.Postgres.ensure_all_started/2 is undefined (module Ecto.Adapters.Postgres is not available)
    (ecto_sql 3.11.0) Ecto.Adapters.Postgres.ensure_all_started([telemetry_prefix: [:blogset, :repo], otp_app: :blogset, timeout: 15000, username: "postgres", password: "postgres", hostname: "localhost", database: "blogset_dev", stacktrace: true, show_sensitive_data_on_connection_error: true, pool_size: 10], :temporary)
    (ecto_sql 3.11.0) lib/ecto/migrator.ex:160: Ecto.Migrator.with_repo/3
    (ecto_sql 3.11.0) lib/mix/tasks/ecto.migrate.ex:151: anonymous fn/5 in Mix.Tasks.Ecto.Migrate.run/2
    (elixir 1.15.5) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ecto_sql 3.11.0) lib/mix/tasks/ecto.migrate.ex:139: Mix.Tasks.Ecto.Migrate.run/2
    (mix 1.15.5) lib/mix/task.ex:447: anonymous fn/3 in Mix.Task.run_task/5
    (mix 1.15.5) lib/mix/cli.ex:92: Mix.CLI.run_task/2
    c:/Program Files/Elixir/bin/mix:2: (file)

C:\CarbonX1\Phoenix\Projets\blogset>

Nous essayons avec mix setup :

C:\CarbonX1\Phoenix\Projets\blogset>mix setup
Resolving Hex dependencies...
Resolution completed in 0.255s
Unchanged:
  castore 1.0.4
  comeonin 5.4.0
  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
  pbkdf2_elixir 2.2.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
All dependencies are up to date
** (UndefinedFunctionError) function Ecto.Adapters.Postgres.__info__/1 is undefined (module Ecto.Adapters.Postgres is not available)
    (ecto_sql 3.11.0) Ecto.Adapters.Postgres.__info__(:attributes)
    (ecto 3.11.0) lib/mix/ecto.ex:142: Mix.Ecto.ensure_implements/3
    (ecto 3.11.0) lib/mix/tasks/ecto.create.ex:52: anonymous fn/3 in Mix.Tasks.Ecto.Create.run/1
    (elixir 1.15.5) lib/enum.ex:984: Enum."-each/2-lists^foreach/1-0-"/2
    (mix 1.15.5) lib/mix/task.ex:447: anonymous fn/3 in Mix.Task.run_task/5
    (mix 1.15.5) lib/mix/task.ex:506: Mix.Task.run_alias/6
    (mix 1.15.5) lib/mix/cli.ex:92: Mix.CLI.run_task/2
    c:/Program Files/Elixir/bin/mix:2: (file)

C:\CarbonX1\Phoenix\Projets\blogset>

Erreur à corriger : Ecto.Adapters.Postgres.info/1 is undefined

Nous retrouvons l’erreur que nous avions déjà eu et résolu dans l’article compîlation windows pour elixir.

Comme la base blogset_dev ne contient pas de données utiles, nous allons supprimer la table et la recréer. Pour se faire nous allons dans pgAdmin.

utilisation de pgAdmin pour supprimer une base de données dans PostgreSQL

Nous lançons pgAdmin dans Windows : Boite de recherche Windows pgAdmin pour trouver l’application.

Une page d'accueil pour le blog Phoenix#1-Ouverture de pgAdmin4
Une page d’accueil pour le blog Phoenix#1-Ouverture de pgAdmin4

Nous avons plusieurs base.

Une page d'accueil pour le blog Phoenix#2-Sélection de la base à supprimer dans pgAdmin4
Une page d’accueil pour le blog Phoenix#2-Sélection de la base à supprimer dans pgAdmin4

Nous supprimons les bases du projet blogset grace au menucontextuel click-droit sur la base.

Une page d'accueil pour le blog Phoenix#3-Suppression de la base avec pgAdmin4
Une page d’accueil pour le blog Phoenix#3-Suppression de la base avec pgAdmin4

On nous demande deconfirmer.

Une page d'accueil pour le blog Phoenix#4-Demande de confirmation avant suppression de la base avec pgAdmin4
Une page d’accueil pour le blog Phoenix#4-Demande de confirmation avant suppression de la base avec pgAdmin4

La base est supprimée.

Une page d'accueil pour le blog Phoenix#5-La base est supprimée avec pgAdmin4
Une page d’accueil pour le blog Phoenix#5-La base est supprimée avec pgAdmin4

Nous pouvons tester le setup du projet pour reconstruire la base complete depuis le projet

Mise à jour complète du projet

Maintenat que nous avons supprimer la base, nous pouvons lancer la création, mais cela ne fonctionne pas. Nous allons tester :

  • supprimer le fichier mix.lock
  • supprimer lle répertoire deps
  • supprimer le repertoire _build
  • relancer mix setup
Une page d'accueil pour le blog Phoenix#6-Suppression des fichiers pour relancer la génération du projet
Une page d’accueil pour le blog Phoenix#6-Suppression des fichiers pour relancer la génération du projet

nous allons dans le repertoire du projet :

  • C:\CarbonX1\Phoenix\Projets\blogset
C:\CarbonX1\Phoenix\Projets\blogset>mix setup
Resolving Hex dependencies...
Resolution completed in 0.464s
New:
  castore 1.0.5
  comeonin 5.4.0
  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.1
  ecto_sql 3.11.1
  esbuild 0.8.1
  expo 0.5.1
  file_system 0.2.10
  finch 0.16.0
  floki 0.35.2
  gettext 0.24.0
  hpax 0.1.2
  jason 1.4.1
  mime 2.0.5
  mint 1.5.2
  nimble_options 1.1.0
  nimble_pool 1.0.0
  pbkdf2_elixir 2.2.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.2
  phoenix_pubsub 2.1.3
  phoenix_template 1.0.4
  plug 1.15.2
  plug_cowboy 2.6.1
  plug_crypto 2.0.0
  postgrex 0.17.4
  ranch 1.8.0
  swoosh 1.14.3
  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 phoenix (Hex package)
* Getting phoenix_ecto (Hex package)
* Getting ecto_sql (Hex package)
* Getting postgrex (Hex package)
* Getting phoenix_html (Hex package)
* Getting phoenix_live_reload (Hex package)
* Getting phoenix_live_view (Hex package)
* Getting floki (Hex package)
* Getting phoenix_live_dashboard (Hex package)
* Getting esbuild (Hex package)
* Getting tailwind (Hex package)
* Getting swoosh (Hex package)
* Getting finch (Hex package)
* Getting telemetry_metrics (Hex package)
* Getting telemetry_poller (Hex package)
* Getting gettext (Hex package)
* Getting jason (Hex package)
* Getting dns_cluster (Hex package)
* Getting plug_cowboy (Hex package)
* Getting cowboy (Hex package)
* Getting cowboy_telemetry (Hex package)
* Getting plug (Hex package)
* Getting mime (Hex package)
* Getting plug_crypto (Hex package)
* Getting telemetry (Hex package)
* Getting cowlib (Hex package)
* Getting ranch (Hex package)
* Getting expo (Hex package)
* Getting castore (Hex package)
* Getting mint (Hex package)
* Getting nimble_options (Hex package)
* Getting nimble_pool (Hex package)
* Getting hpax (Hex package)
* Getting phoenix_template (Hex package)
* Getting file_system (Hex package)
* Getting db_connection (Hex package)
* Getting decimal (Hex package)
* Getting ecto (Hex package)
* Getting phoenix_pubsub (Hex package)
* Getting websock_adapter (Hex package)
* Getting websock (Hex package)
* Getting comeonin (Hex package)
You have added/upgraded packages you could sponsor, run `mix hex.sponsor` to learn more
==> file_system
Compiling 7 files (.ex)
Generated file_system 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 22 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_reload
Compiling 4 files (.ex)
warning: Logger.warn/1 is deprecated. Use Logger.warning/2 instead
  lib/phoenix_live_reload/application.ex:32: Phoenix.LiveReloader.Application.start_link/0

Generated phoenix_live_reload 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 33 files (.ex)
Generated blogset app
The database for Blogset.Repo has been created

17:36:17.837 [info] == Running 20231130151218 Blogset.Repo.Migrations.CreateUsersAuthTables.change/0 forward

17:36:17.837 [info] execute "CREATE EXTENSION IF NOT EXISTS citext"

17:36:17.877 [info] create table users

17:36:17.889 [info] create index users_email_index

17:36:17.892 [info] create table users_tokens

17:36:17.902 [info] create index users_tokens_user_id_index

17:36:17.904 [info] create index users_tokens_context_token_index

17:36:17.907 [info] == Migrated 20231130151218 in 0.0s

17:36:17.911 [info] == Running 20231201102833 Blogset.Repo.Migrations.CreateStories.change/0 forward

17:36:17.911 [info] create table stories

17:36:17.921 [info] create index stories_user_id_index

17:36:17.924 [info] == Migrated 20231201102833 in 0.0s
[debug] Downloading tailwind from https://github.com/tailwindlabs/tailwindcss/releases/download/v3.3.2/tailwindcss-windows-x64.exe
[debug] Downloading esbuild from https://registry.npmjs.org/@esbuild/win32-x64/0.17.11

Rebuilding...

Done in 441ms.

  ..\priv\static\assets\app.js  218.7kb

Done in 33ms

C:\CarbonX1\Phoenix\Projets\blogset>

Nous pouvons aller voir avec pgAdmin la création de la base :

Une page d'accueil pour le blog Phoenix#7-La base a été créée avec toutes les tables
Une page d’accueil pour le blog Phoenix#7-La base a été créée avec toutes les tables

Vérification du projet

Commençons par vérifier les tests :

  • cd C:\CarbonX1\Phoenix\Projets\blogset
  • 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 22 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 37 files (.ex)
Generated blogset app
..............................................................................................................................................
Finished in 1.2 seconds (0.7s async, 0.4s sync)
142 tests, 0 failures

Randomized with seed 290487

C:\CarbonX1\Phoenix\Projets\blogset>

Tous les tests passent avec succès. Executons le projet pour voir la page. Nous devons ouvrir la boite de commande en mode Admin pour activer symlink au moins la premiere fois.

Vérification en se connectant avec un navigateur

  • cd C:\CarbonX1\Phoenix\Projets\blogset ‘dossier du projet
  • code . ‘ouvrir vs code
  • iex -S mix phx.server ‘execution en mode interactif
  • http://localhost:4000/stories ‘page avec la liste des articles

Nous constatons que l’accès à la page avec les articles (stories) est protégé :

Une page d'accueil pour le blog Phoenix#8-L'accès à la page stories est protégée
Une page d’accueil pour le blog Phoenix#8-L’accès à la page stories est protégée
  • http://localhost:4000/ ‘page index

La page principale présente les 2 boutons permettant de créer un compte :

Une page d'accueil pour le blog Phoenix#9-La page avec les boutons d'authentification
Une page d’accueil pour le blog Phoenix#9-La page avec les boutons d’authentification

Créons maintenant un compte [Register] ci-dessus.

Une page d'accueil pour le blog Phoenix#10-Création d'un compte
Une page d’accueil pour le blog Phoenix#10-Création d’un compte

Nous pouvons maaintenent alles sur la page des articles et nous voyons bien en haut à droite que notre session est identifiée.

Une page d'accueil pour le blog Phoenix#11-Compte identifié dans la session
Une page d’accueil pour le blog Phoenix#11-Compte identifié dans la session

Nous pouvons créer un article [Story].

Une page d'accueil pour le blog Phoenix#12-Créer un article
Une page d’accueil pour le blog Phoenix#12-Créer un article

Vérifions maintenant comment est enregistré notre article dans la base de données.

  • cd C:\CarbonX1\Phoenix\Projets\blogset ‘dossier du projet
  • 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
  • select * from stories; ‘afficher la table stories contenant les articles
  • \q ‘pour quitter psql
C:\Users\broussel>psql -U postgres
Mot de passe pour l'utilisateur postgres :
psql (16.1)
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 | 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=# select * from stories;
 id |      title       |        body         | user_id |     inserted_at     |     updated_at
----+------------------+---------------------+---------+---------------------+---------------------
  1 | Article numÚro 1 | Mon premier article |       1 | 2024-01-03 10:52:32 | 2024-01-03 10:52:32
(1 ligne)


blogset_dev=# select * from users;
 id |     email      |                                                           hashed_password                                                           | confirmed_at |     inserted_at     |     updated_at
----+----------------+-------------------------------------------------------------------------------------------------------------------------------------+--------------+---------------------+---------------------
  1 | test@gmail.com | $pbkdf2-sha512$160000$biq8ifwX27Kv5qvBNOGjpg$BbsIO29Y3eLO9mh1iY4.jtnX/QmEcvTHTk1v2JGHFsvzQyXx6yfMNiqtvkNp8jK6luSKZL0PDiOSwUuG0oLMkg |              | 2024-01-03 10:33:52 | 2024-01-03 10:33:52
(1 ligne)


blogset_dev=# \q

C:\Users\broussel>

Le premier utilisateur a un id=1, et l’article associé à notre utilisateur a bien la référence à user_id=1.

Vérifions que les auteurs ne voient que leurs propres articles. Regardons dans le code du projet :

  • cd C:\CarbonX1\Phoenix\Projets\blogset ‘dossier du projet
  • code . ‘ouvrir vs code

BLOGSET/lib/blogset/stories.ex :

  #la liste accéssible à tous
  def list_stories do
    Repo.all(Story)
  end

  #la liste des articles de l'utilisateur
  def list_stories(user_id) do
    Repo.all(from s in Story, where: s.user_id == ^user_id)
  end
  
  #accès à un article pour tout le monde
  def get_story!(id), do: Repo.get!(Story, id)

  #accès à l'article que pour l'auteur
  def get_story!(id, user_id), do: Repo.one(from s in Story, where: s.id == ^id and s.user_id == ^user_id)

BLOGSET/lib/blogset_web/live/story_live/index.ex :

  @impl true
  def mount(_params, _session, socket) do
    Logger.info(current_user_mount: socket.assigns[:current_user].id)
    {:ok, stream(socket, :stories, Stories.list_stories(socket.assigns[:current_user].id))}
  end
  
    defp apply_action(socket, :edit, %{"id" => id}) do
    socket
    |> assign(:page_title, "Edit Story")
    |> assign(:story, Stories.get_story!(id, socket.assigns.current_user.id))
  end

Nous avons ajouté la référence à l’utilisateur connecté aussi bien dans mount(_, _, _) que dans apply_action(_ , :edi, _). La référence à l’utilisateur est dans socket.assigns.

Créons un second utilisateur, pour vérifier le comportement de l’application.

Vérification en modifiant l’url

Nous devons vérifier que la requête sur un article ne soit pas accessible pour un autre utilisateur que l’auteur :

  • http://localhost:4000/stories/1/edit ‘article id=1
  • doit être accessible que par l’auteur user_id=1 soit : test@gmail.com

La requête est cellle qui permet d’afficher en édition stories avec id=1

Une page d'accueil pour le blog Phoenix#13-Accès impossible à un article dont on n'est pas l'auteur
Une page d’accueil pour le blog Phoenix#13-Accès impossible à un article dont on n’est pas l’auteur

Voir les articles dans la page d’accueil du blog Phoenix

Maintenant que nous avons vérifié la sécurité de notre application, nous devons permettre à des visiteurs de voir les articles du blog.

Cela suppose :

  • de créer une page dédiée pour regrouper tous les articles
  • de supprimer de cette page les boutons de modifications et suppression des articlles
  • d’ajouter un lien pour voir l’article complet en lecture seule
  • de créer une page pour l’article complet
  • de mettre les pages liste et détail en accès libre dans le router
  • d’obtenir depuis lle context Stories la liste des articles et le détail d’un article

La création des articles avec mix phx.gen.live Stories Story stories a généré les 2 pages liste et détaill. Nous avons modifié la page liste créé pour la personnalisée pour chaque auteur, afin que les auteurs puissent voir la liste de leurs propres article et le modifier ou les supprimer.

Créer une copie de la page index pour créer une page d’accueil pour le blog Phoenix

Commençons par créer une copie de la page liste des articles en l’appelant home.

  • cd C:\CarbonX1\Phoenix\Projets\blogset ‘ouvrir le répertoire du projet
  • code . ‘ouvrir VS Code
  • BLOGSET/lib/blogset_web/live/story_live/index ‘fichier avec la liste des articles à copier
  • BLOGSET/lib/blogset_web/live/story_live/home ‘fichier avec la liste des articles à créer

Nous devons bien sûr copier aussi le fichier de rendu index.html.heex :

Une page d'accueil pour le blog Phoenix#14-Copie du fichier index en home
Une page d’accueil pour le blog Phoenix#14-Copie du fichier index en home

La premiere chose à faire lorsque nous copions-collons un fichier en le renommant, c’est de changer le nom du module. Ici , nous ouvrons le fichier BLOGSET/lib/blogset_web/live/story_live/home.ex, et renommons la première ligne :

  • defmodule BlogsetWeb.StoryLive.Index do ‘avant
  • defmodule BlogsetWeb.StoryLive.Home do ‘après

Comme nous souhaitons accéder à cette page avec live « / », StoryLive.Home, :home, nous changeons aussi la fonction handle_action pour prendre en compte :home au lieu de :index :

  defp apply_action(socket, :home, _params) do   ":home remplace :index
    socket
    |> assign(:page_title, "Listing Stories")
    |> assign(:story, nil)
  end

Cette page doit donner la liste des articles sans avoir besoin de s’authentifier. Nous supprimons la référence au user.id dans mount :

  @impl true
  def mount(_params, _session, socket) do
    # Logger.info(current_user_mount: socket.assigns[:current_user].id) "suppression de la trace Logger
    # {:ok, stream(socket, :stories, Stories.list_stories(socket.assigns[:current_user].id))} #avant
    {:ok, stream(socket, :stories, Stories.list_stories())}                                   #après
  end

La fonction Stories.list_stories/0 est bien présente dans le module Blogset.Stories :

defmodule Blogset.Stories do
  @moduledoc """
  The Stories context.
  """

  import Ecto.Query, warn: false
  alias Blogset.Repo

  alias Blogset.Stories.Story

  require Logger

  @doc """
  Returns the list of stories.

  ## Examples

      iex> list_stories()
      [%Story{}, ...]

  """
  def list_stories do
    Repo.all(Story)
  end

Pour donner l’accès à la page StoryLive.Home, nous déclarons cette page dans le router avec l’accès sur l’url de base : ‘/’

Ajout de la page StoryLive.Home dans le router

BLOGSET/lib/blogset_web/router.ex :

  scope "/", BlogsetWeb do
      pipe_through :browser

  #   get "/", PageController, :home    #ligne avant la modification
      live "/", StoryLive.Home, :home  #accès à la liste des articles de la page StoryLive.Home
  end

Nous pouvons maintenant tester cette page, mais pour bien voir la différence entre la page StoryLive.Home et la page StoryLive.Index, nous modifions le titre de notre page dans le fichier html avec « Liste de tous les articles » :

BLOGSET/lib/blogset_web/live/story_live/home.html.heex :

<.header>
  Liste de tous les articles
  <:actions>
    <.link patch={~p"/stories/new"}>
      <.button>New Story</.button>
    </.link>
  </:actions>
</.header>

Nous pouvons maintenant accéder à notre blog :

  • cd C:\CarbonX1\Phoenix\Projets\blogset ‘se placer dans le dosseir du projet
  • iex -S mix phx.server ‘activer le serveur avec le projet blogset
  • http://localhost:4000/ ‘aller sur la page d’accueil
Une page d'accueil pour le blog Phoenix#15-Liste de tous les articles dans la page d'accueil
Une page d’accueil pour le blog Phoenix#15-Liste de tous les articles dans la page d’accueil

Modification du contenu de la page d’accueil pour le blog Phoenix : StoryLive.Home

La page que nous avons copiée contient des actions générées automatiquement. Nous adaptons la page à nos besoins :

BLOGSET/lib/blogset_web/live/story_live/home.ex :

  • Edit ‘ apply_action(_, :edit, _) à supprimer de home.ex
  • Delete ‘  handle_event(« delete », _, _) à supprimer
  • New ‘ apply_action(_, :new, _) à supprimer de home.ex
  • Save ‘ handle_info({_, {:saved, _}}, _)

Nous devrons ensuite ajouter Voir l’article correspondant à l’action Show.

BLOGSET/lib/blogset_web/live/story_live/home.html.heex :

<.header>
  Liste de tous les articles
  <:actions>
    <.link patch={~p"/stories/new"}>
      <.button>New Story</.button>
    </.link>
  </:actions>
</.header>

<.table
  id="stories"
  rows={@streams.stories}
  row_click={fn {_id, story} -> JS.navigate(~p"/stories/#{story}") end}
>
  <:col :let={{_id, story}} label="Title"><%= story.title %></:col>
  <:col :let={{_id, story}} label="Body"><%= story.body %></:col>
  <:action :let={{_id, story}}>
    <div class="sr-only">
      <.link navigate={~p"/stories/#{story}"}>Show</.link>
    </div>
    <.link patch={~p"/stories/#{story}/edit"}>Edit</.link>
  </:action>
  <:action :let={{id, story}}>
    <.link
      phx-click={JS.push("delete", value: %{id: story.id}) |> hide("##{id}")}
      data-confirm="Are you sure?"
    >
      Delete
    </.link>
  </:action>
</.table>

Dans le header, nous supprimons l’action New (lignes 3 à 7).

Dans la partie <table> nous supprimons les actions Edit (lignes 21) et Edit (lignes 23 à 30) et nous enlevons le div pour laisser Show (lignes 18 et 20).

Une page d'accueil pour le blog Phoenix#16-La page d'accueil sans les actions réservées aux auteurs
Une page d’accueil pour le blog Phoenix#16-La page d’accueil sans les actions réservées aux auteurs

Activation de la page pour lire les articles

Pour lire les articles nous avons une page Show.ex qui a été générée ainsi que sont Show.html.heex. Nous devons faire en sorte que le boutons dans la page Home active le chargement de la page. Nous devons intervenir au niveau du router. La ligne :

  • live « /stories/:id », StoryLive.Show, :show

doit être mis dans la zone libre, et non dans la zone sous protection require_authenticated_user.

  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

      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
  end

Nous avons besoin de sortir la ligne 13 dans une zone libre. Nous pouvons mettre cela au début des routes, mais cela va entrer en conflit avec la route de la ligne 10 : live « /stories/new », StoryLive.Index, :new.

Si nous regardons bien, « /stories/:id » entre en conflit avec « /stories/new » en affectant new à :id. C’est pourquoi nous devons mettre les routes dans l’ordre.

Aussi, nous ajoutons la route « /stories/:id » dans une zone libre en dessous de la zone protégée require_authenticated_user, pour ne pas entrer en conflit avec « /stories/new » :

  scope "/", BlogsetWeb do
      pipe_through :browser

      live "/stories/:id", StoryLive.Show, :show
  end

Phoenix contrôle les routes et affiche un message en cas de conflit dans l’ordre des clauses :

warning: this clause cannot match because a previous clause at line 76 always matches
  lib/blogset_web/router.ex:84

Maintenant nous pouvons voir la page détail de l’article :

Une page d'accueil pour le blog Phoenix#17-La page détail de l'article
Une page d’accueil pour le blog Phoenix#17-La page détail de l’article

Nous devons maintenant adapter cette page en supprimant le bouton Edit Story, et en faisant en sorte que le lien Back to stories retourne à la page d’accueil.

BLOGSET/lib/blogset_web/live/story_live/show.html.heex :

<.header>
  Story <%= @story.id %>
  <:subtitle>This is a story record from your database.</:subtitle>
  <:actions>
    <.link patch={~p"/stories/#{@story}/show/edit"} phx-click={JS.push_focus()}>
      <.button>Edit story</.button>
    </.link>
  </:actions>
</.header>

<.list>
  <:item title="Title"><%= @story.title %></:item>
  <:item title="Body"><%= @story.body %></:item>
</.list>

<.back navigate={~p"/stories"}>Back to stories</.back>

<.modal :if={@live_action == :edit} id="story-modal" show on_cancel={JS.patch(~p"/stories/#{@story}")}>
  <.live_component
    module={BlogsetWeb.StoryLive.FormComponent}
    id={@story.id}
    title={@page_title}
    action={@live_action}
    story={@story}
    patch={~p"/stories/#{@story}"}
  />
</.modal>

Nous supprimons le popup <.mlodal></.modal> qui est utilisé pour editer l’article (ligne18 à 26), ainsi que le bouton qui active l’edition de l’article (ligne 4 à 8).

Nous modifions la ligne 16 pour que le retour se fasse directement sur la page d’accueil Home :

  • <.back navigate={~p »/stories« }>Back to stories ‘avant modification ligne 16
  • <.back navigate={~p »/ »}>Back to stories ‘après modification ligne 16

Compléter le menu avec de nouveaux liens

Pour simplifier la navigation, nous avons besoin de nouveaux liens pour accéder à la page d’accueil ou à la liste des articles de l’auteur.

Pour l’instant, nous avons 3 boutons dans le menu :

  • Log out
  • Log In
  • Register
  • Setting

Nous allons ajouter :

  • Home
  • Mes articles

La description du menu se trouve dans le fichier

BLOGSET/lib/blogset_web/components/layout/root.html.heex :

  <body class="bg-white antialiased">
    <ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
      <%= if @current_user do %>
        <li class="text-[0.8125rem] leading-6 text-zinc-900">
          <%= @current_user.email %>
        </li>
        <li>
          <.link
            href={~p"/users/settings"}
            class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
          >
            Settings
          </.link>
        </li>
        <li>
          <.link
            href={~p"/users/log_out"}
            method="delete"
            class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
          >
            Log out
          </.link>
        </li>
      <% else %>
        <li>
          <.link
            href={~p"/users/register"}
            class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
          >
            Register
          </.link>
        </li>
        <li>
          <.link
            href={~p"/users/log_in"}
            class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
          >
            Log in
          </.link>
        </li>
      <% end %>
    </ul>
    <%= @inner_content %>
  </body>
</html>

Nous compllétons la liste <li></li> (ligne 23) avec les liens <.link></.link> en adaptant href et le libellé du texte :

        <li>
          <.link
            href={~p"/"}
            class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
          >
            Home
          </.link>
        </li>
        <li>
          <.link
            href={~p"/stories"}
            class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
          >
            Mes articles
          </.link>
        </li>

Remarque, nous voyons que la liste est construite avec une option pour le cas ou l’utilisateur est identifié (ligne 3 à 23) et une option pour les visiteurs non identifiés (ligne 25 à 40).

Une page d'accueil pour le blog Phoenix#18-Les nouveaux menus dans la page
Une page d’accueil pour le blog Phoenix#18-Les nouveaux menus dans la page

La page d’accueil pour le blog Phoenix est opérationnelle

Nous avons maintenant terminée la page d’accueiol du blog. Nous avons :

  • créée une page accessible à tous les utilisateurs
  • ajouté des boutons dans le menu pour simplifier la navigation
  • vérifier les accès pour les auteurs d’articles
Si vous avez aimé l'article vous êtes libre de le partager :-)

Laisser un commentaire