Gestion des articles - Créer un blog avec Adonis
Tags: webdev, adonisjs, javascript, tutorial
Published: 2021-08-21 10:14:43

Bonjour,

Bienvenue dans ce tutoriel pour apprendre à utiliser le [framework web Adonis](https://adonisjs.com/) ! Si tu souhaites en savoir plus sur Adonis en 1 coup œil, je t'invite à [lire cette page](https://adonisjs.com/adonisjs-at-a-glance).

Dans cette partie, on va voir ensemble comment créer un article, le lier à un utilisateur et afficher sur une page !

## Rappel

Ce tutoriel est la cinquième et dernière partie d'une série de tutoriels qui ont pour objectif de te faire découvrir Adonis au travers la création d'un blog.

Pour lire la partie précédente, c'est par là [Création et visualisation des articles - Créer un blog avec Adonis](https://dev.to/barbapapazes/creation-et-visualisation-des-articles-creer-un-blog-avec-adonis-1d8k) 

Tu trouveras aussi sur GiHub l'ensemble du [code source du projet](https://github.com/Barbapapazes/create-a-blog-using-adonis) !

### Sommaire

Ce tutoriel est découpé en différente partie pour t'aider et pour éviter d'avoir des articles trop longs où l'on pourrait se perdre !

Nous allons donc voir ensemble :

- [Objectifs et mise en route - Créer un blog avec Adonis](https://dev.to/barbapapazes/objectifs-et-mise-en-route-creer-un-blog-avec-adonis-20lg)
- [Création d'un utilisateur - Créer un blog avec Adonis](https://dev.to/barbapapazes/creation-d-un-utilisateur-creer-un-blog-avec-adonis-1efi)
- [Création de l'authentification pour l'utilisateur - Créer un blog avec Adonis](https://dev.to/barbapapazes/creation-de-l-authentification-pour-l-utilisateur-creer-un-blog-avec-adonis-4mm8)
- [Création et visualisation des articles - Créer un blog avec Adonis](https://dev.to/barbapapazes/creation-et-visualisation-des-articles-creer-un-blog-avec-adonis-1d8k)
- [Gestion des articles - Créer un blog avec Adonis](https://dev.to/barbapapazes/gestion-des-articles-creer-un-blog-avec-adonis-2fc0)

Finalement, tu auras un blog fonctionnel !

## Gestion des articles

Comme le chapitre précédent, nous allons faire ce chapitre sous la forme d'un exercice. Je vais t'indiquer les étapes à suivre, te donner des indications et des ressources qui peuvent t'aider pour aller au bout puis nous ferons la correction ensemble !

Pour rappel, nous souhaitons réaliser une route `articles/create` qui permet de créer un article. Une fois l'article enregistré, nous devons être redirigé sur la page de l'article. Aussi, nous souhaitons avoir un bouton sur les pages des articles qui permet d'éditer l'article. Une fois l'édition terminée, nous devons être redirigé vers l'article.

### Ce qu'il faut faire

La liste suivante est l'ensemble des tâches ordonnées t'indiquant ce qu'il y a à faire pour arriver au bout de cette dernière partie :

1. Mettre en place le middleware d'authentification
    - Il ne faut le placer que sur les routes qui ont besoin d'être protégées
    - Un peu d'aide [ici](https://docs.adonisjs.com/guides/controllers#applying-middleware) et [là](https://docs.adonisjs.com/guides/auth/middleware) !
2. Créer les différentes méthodes de gestions des articles pour le `controller`
    - Il ne faut pas oublier de valider les données entrantes sur le serveur
    - Il ne faut pas non plus oublier de gérer les erreurs
    - Un indice [ici](https://docs.adonisjs.com/guides/controllers#resourceful-routes-and-controllers)
3. Créer les différentes vues pour la création et l'édition d'un article

### Gestion des articles

#### Mise en place du middleware d'authentification

Le middleware pour la gestion de l'authentification est fourni par Adonis. Ainsi, il nous suffit de l'ajouter sur les routes que l'on souhaite protéger par l'authentification.

Pour cela, on se rend dans le fichier `routes.ts` du dossier `start` :

```jsx
Route.resource('articles', 'ArticlesController').middleware({
  create: ['auth'],
  store: ['auth'],
  edit: ['auth'],
  update: ['auth'],
})
```

La route `create` permet d'afficher le formulaire de création d'un article.

La route `store` permet de recevoir les informations de la route create pour stocker l'article dans la base de données.

La route `edit` permet d'afficher le formulaire de l'édition d'un article.

La route `update` permet de recevoir les informations de la route edit pour mettre à jour un article de la base de données.

Ainsi, il est impossible d'accéder à l'une de ses quatres routes si l'utilisateur n'est pas authentifié.

#### Création du `validator` pour les articles

Encore une fois, utilisons la commande :

```bash
node ace make:validator article
```

Dans le validator, on va vouloir l'unicité des titres de nos articles comme on l'a indiqué dans notre migration. On va aussi spécifier la taille maximale du titre et du contenu. Ainsi, le `validator` ressemble à cela :

```bash
public schema = schema.create({
  title: schema.string({ trim: true }, [
    rules.maxLength(256),
    rules.unique({ table: 'articles', column: 'title' }),
  ]),
  content: schema.string({ trim: true }, [rules.maxLength(1024)]),
})
```

Ensuite, on personnalise les messages erreurs et le tour est joué !

```bash
public messages = {
  'title.required': 'Le titre est requis',
  'title.string': 'Le titre doit être une chaîne de caractères',
  'title.maxLength': 'Le titre doit être moins de 256 caractères',
  'title.unique': 'Le titre existe déjà',
  'content.required': 'Le contenu est requis',
  'content.string': 'Le contenu doit être une chaîne de caractères',
  'content.maxLength': 'Le contenu doit être moins de 1024 caractères',
}
```

Pour en savoir plus : [unique](https://docs.adonisjs.com/reference/validator/rules/unique), [validator](https://docs.adonisjs.com/guides/validator/introduction), [messages](https://docs.adonisjs.com/guides/validator/custom-messages)

#### Création des méthodes du controller des articles

On va donc devoir créer une méthode pour chacune de ses routes. Il est simple de s'y repérer puisque le nom de la méthode est le nom de la route.

Pour `create` : 

```jsx
public async create({ view }: HttpContextContract) {
  return view.render('articles/create')
}
```

Rien de sorcier ici, on crée la page contenant le formulaire de création. Nous allons créer la vue dans la suite.

Pour `store`, on commence par récupérer les informations dont on a besoin pour créer l'article :

```jsx
const { user } = auth
const { title, content } = await request.validate(ArticleValidator)
```

Il est possible de récupérer l'utilisateur grâce au middleware précédemment ajouté. En effet, le middleware vérifie l'identité de la personne et l'ajoute dans nos `controllers`. On récupère aussi les informations dont on a besoin, validées par le `validator` ! 

Ensuite, on crée un article : 

```jsx
const article = await Article.create({
    title,
    content,
    ownerId: user!.id,
  })
```

Puis on charge le owner avant de rendre la page d'un unique article à l'utilisateur :

```jsx
await article.load('owner')

return view.render('articles/show', {
  article,
})
```

Pour finir, la méthode ressemble à cela :

```jsx
public async store({ request, view, auth }: HttpContextContract) {
  const { user } = auth
  const { title, content } = await request.validate(ArticleValidator)

  const article = await Article.create({
    title,
    content,
    ownerId: user!.id,
  })

  await article.load('owner')

  return view.render('articles/show', {
    article,
  })
}
```

Pour `edit`, nous allons aussi rendre une page d'édition de l'article. Cependant, avant cela, nous devons utiliser le paramètre présent dans l'url pour récupérer l'article et ajouter son contenu au formulaire afin que ce dernier soit prérempli ! Si le formulaire est vide, comment savoir ce qu'il faut mettre à jour ?

```jsx
public async edit({ view, params }: HttpContextContract) {
  const { id } = params

  let article: Article
  try {
    article = await Article.findOrFail(id)
  } catch (error) {
    console.error(error)
    return view.render('errors/not-found')
  }

  return view.render('articles/edit', {
    article,
  })
}
```

En cas d'erreur pour trouver l'article, et comme on a pu faire sur `show`, on renvoie l'utilisateur sur une page d'erreur ! Cela permet de gérer le cas où l'utilisateur rendre une mauvaise valeur directement dans l'url !

`Update` est la méthode la plus complexe mais qui ne contient que  des éléments que l'on a pu déjà aborder. Dans un premier temps, on récupère l'article que l'on veut mettre à jour :

```jsx
const { id } = params

let article: Article
try {
  article = await Article.findOrFail(id)
} catch (error) {
  console.error(error)
  return view.render('errors/not-found')
}
```

Ensuite, on extrait des données du formulaire en les passant dans le validator : 

```jsx
const { title, content } = await request.validate(ArticleValidator)
```

Puis on met à jour l'article et on le sauvegarde :

```jsx
article.title = title
article.content = content

await article.save()
```

Ensuite, on charge l'utilisateur dans l'article et on le redirige vers la page de visualisation de son article : 

```jsx
await article.load('owner')

return view.render('articles/show', {
  article,
})
```

Finalement, la méthode complète ressemble à cela :

```jsx
public async update({ request, view, params }: HttpContextContract) {
  const { id } = params

  let article: Article
  try {
    article = await Article.findOrFail(id)
  } catch (error) {
    console.error(error)
    return view.render('errors/not-found')
  }

  const { title, content } = await request.validate(ArticleValidator)

  article.title = title
  article.content = content

  await article.save()
  await article.load('owner')

  return view.render('articles/show', {
    article,
  })
}
```

Maintenant que tous nos `controllers` sont faits, passons à nos 2 vues !

#### Création des vues pour la gestion des articles

Les pages `create` et `edit` vont être très similaires. Elles vont toutes les 2 contenir un formulaire, vide pour la première et avec un article pour la seconde.

Pour `create` :

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Créer un article</title>
</head>
<body>
  <form action="{{ route('ArticlesController.store') }}" method="post">
    <div>
      <label for="title">Titre de l'article</label>
      <input type="text" name="title" id="title" value="{{ flashMessages.get('title', '') }}">
      @if(flashMessages.has('errors.title'))
      <div>{{ flashMessages.get('errors.title') }}</div>
      @endif
    </div>
    <div>
      <label for="content">Contenu de l'article</label>
      <textarea name="content" id="content" cols="30" rows="10" value="{{ flashMessages.get('content', '') }}"></textarea>
      @if(flashMessages.has('errors.content'))
      <div>{{ flashMessages.get('errors.content') }}</div>
      @endif
    </div>
    <button type="submit">Créer</button>
  </form>
</body>
</html>
```

Comme pour le formulaire d'authentification, on remarque les `if` qui permettent de conditionner l'affichage d'un message d'erreur par le serveur.

Pour `edit` :

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Créer un article</title>
</head>
<body>
  <form action="{{ route('ArticlesController.update', {id: article.id}) }}?_method=put" method="post">
    <div>
      <label for="title">Titre de l'article</label>
      <input type="text" name="title" id="title" value="{{ flashMessages.get('title', article.title) }}">
      @if(flashMessages.has('errors.title'))
      <div>{{ flashMessages.get('errors.title') }}</div>
      @endif
    </div>
    <div>
      <label for="content">Contenu de l'article</label>
      <textarea name="content" id="content" cols="30" rows="10">{{ flashMessages.get('content', article.content) }}</textarea>
      @if(flashMessages.has('errors.content'))
      <div>{{ flashMessages.get('errors.content') }}</div>
      @endif
    </div>
    <button type="submit">Modifier</button>
  </form>
</body>
</html>
```

Il y a tout de même quelques subtilités dans ce formulaire. Tout d'avoir cela : 

```jsx
{{ flashMessages.get('content', article.content) }}
```

Cela permet d'afficher le contenu d'un message flash s'il existe, sinon, le contenu de l'article. Ainsi, lorsque la page est chargée, le message flash est vide donc le contenu de l'article va venir remplir le formulaire. Mais supposons que l'utilisateur modifie des informations mais que ces dernières soient rejetées par le `validator`, alors il retrouvera dans son formulaire les données qu'il avait saisi et non les données initiales de l'article !

Ensuite, pour mettre à jour un article, il est obligatoire d'utiliser les verbes `PUT` ou `PATCH`. Cependant, les navigateurs ne les prennent pas en compte. Ainsi, on utilise ce petit tricks pour que cela fonctionne : 

```html
<form action="{{ route('ArticlesController.update', {id: article.id}) }}?_method=put" method="post">
```

[La documentation](https://docs.adonisjs.com/guides/request#form-method-spoofing) explique très bien cela ! Et pour que cela soit fonctionnel, il faut se rendre dans le fichier `app.ts` présent dans `config` et passer la clé `allowMethodSpoofing` à `true` !

Et enfin, nous allons ajouter un lien de modification de l'article lorsque l'utilisateur est connecté dans la vue `show.edge` :

```html
@if(auth.isLoggedIn)
  <a href="{{ route('ArticlesController.edit', {id: article.id}) }}">Editer l'article</a>
@endif
```

Pour en savoir plus : [create](https://docs.adonisjs.com/reference/orm/base-model#static-create), [save](https://docs.adonisjs.com/reference/orm/base-model#save), [load](https://docs.adonisjs.com/reference/orm/base-model#load), [spoofing](https://docs.adonisjs.com/guides/request#form-method-spoofing), [isLoggedIn](https://docs.adonisjs.com/guides/auth/web-guard#isloggedin)

### Bonus

La page `index.edge` dans le dossier `resources/views` ne nous sert pas. Alors on peut supprimer le fichier et dans nos routes, nous allons la rediriger vers nos articles comme cela :

```jsx
Route.get('/', async ({ response }) => {
  return response.redirect('/articles')
})
```

Si l'on se rend sur [http://localhost:3333/](http://localhost:3333/), on est alors redirigé vers [http://localhost:3333/articles](http://localhost/articles) !

Sur notre page `index.edge` dans le dossier `resources/views/articles`, il est possible de mettre un bouton de création d'un article uniquement lorsque l'utilisateur est connecté ! En effet, même si la route est protégée, on ne souhaite pas que tous nos lecteurs voit un bouton de création d'articles alors que l'action n'est pas possible pour eux. Ajoutons cela à notre fichier :

```jsx
@if(auth.isLoggedIn)
  <a href="{{ route('ArticlesController.create') }}">Créer un article</a>
@endif
```

Et voilà, le tour est joué. Le lien ne va apparait que lorsque l'utilisateur est connecté grâce à la condition !

## Conclusion

Et voilà pour cette cinquième et dernière partie. On a vu plus en détail la création et la gestion de pages protégées par un middleware.

N'hésite pas à commenter si tu as des questions, si ça t'a plus ou même pour me faire des retours !

Et tu peux aussi me retrouver sur [Twitter](https://twitter.com/SOUBIRAN25) ou sur [LinkedIn](https://www.linkedin.com/in/esteban25/) !

Notre blog est maintenant terminé ! J'espère que ça t'a plus. N'hésite pas à commenter pour me dire ce que tu as pensé de la série !

À bientôt pour de nouveaux tutoriels !

### Possibles évolutions

Je n'allais quand même pas te laisser sans un peu de choses à faire. Voici une liste d'amélioration pour ce blog qui vont te permettre de progresser dans la maitrise du framework :

- Mettre en place un draft mode.
    - Pour cela, on peut ajouter une propriété à nos articles nommée `isPublished`
    - Si cette propriété est à false, alors l'article ne sera pas disponible pour les utilisateurs non authentifiés. On utilisera alors un filtre si l'utilisateur n'est pas login. Un peu d'aide [ici](https://docs.adonisjs.com/guides/auth/web-guard#isloggedin) et [là](https://docs.adonisjs.com/reference/database/query-builder#where) !
    - Si la propriété est à true, alors l'article est visible.
    - Lors de l'édition et de la création, on peut avoir la possibilité de changer cela via une checkbox.
- Mettre des images aux articles
    - Pour cela, tu peux utiliser [cela](https://docs.adonisjs.com/guides/file-uploads) !
- Ajouter du style à ton blog avec un peu de CSS
    - Je te conseille alors de te renseigner sur le [module de gestion des assets](https://docs.adonisjs.com/guides/assets-manager) de Adonis
- On peut créer une page de profil pour l'utilisateur connecté et lui permettre de voir l'ensemble des articles qu'il a écrit !
    - [Cela](https://docs.adonisjs.com/reference/orm/decorators#hasmany) pourrait t'aider !
- Il est aussi possible de se dire que l'on peut créer une page où les utilisateurs peuvent se connecter et créer des articles, comme sur medium. Mais il ne faudrait alors pas
- Et pour les plus motivé, on peut même imaginer créer un blog
    - Les lecteurs entre un pseudo, un message et publie le commentaire
    - On peut même imaginer que les lecteurs doivent se créer un compte !

Et voici un bon nombre d'idées pour améliorer le blog et pour te montrer que tu peux dorénavant faire beaucoup de choses sur le web ! Et je suis certains que ton imagination a encore pleins d'idées !

Je peux aussi te conseil d'aller voir [ce repo](https://github.com/adonisjs-community/polls-app) qui contient une application real-world et qui te montre comment faire et en plus, c'est par le créateur d'Adonis !

Bye ! 👋