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.
Nous avons plusieurs base.
Nous supprimons les bases du projet blogset grace au menucontextuel click-droit sur la base.
On nous demande deconfirmer.
La base est supprimée.
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
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 :
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é :
- http://localhost:4000/ ‘page index
La page principale présente les 2 boutons permettant de créer un compte :
Créons maintenant un compte [Register] ci-dessus.
Nous pouvons maaintenent alles sur la page des articles et nous voyons bien en haut à droite que notre session est identifiée.
Nous pouvons créer un article [Story].
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
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 :
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
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).
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 :
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).
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