# VENoM : Vue Express Node MongoDB

# Rappels sur JavaScript, Vue et Vue-router

Cf. partie dédiée

# Vuex

# Qu'est-ce qu'un état de l'application

L'état de l'application est un objet contenant des données concernant toute l'application, autrement dit, des données que plusieurs composants de l'application pourront consulter.

On parle de store pour désigner une bibliothèque de gestion de l'état : un store va contenir l'état et les méthodes pour modifier cet état.

# La solution officielle de l'écosystème Vue : Vuex

Vuex 4 (opens new window) est la librairie officielle pour gérer le store dans une application Vue. Attention, pour la version 3 de vue (que je recommande d'utiliser), il faut la version 4.

# Les différentes parties du store :

Un store est composé de plusieurs parties :

  • l'état, qui est un objet dans la propriété state dont les propriétés sont réactives ;
  • les mutations, qui sont dans un objet dans la propriété mutations dont les propriétés sont des fonctions qui vont changer l'état ;
  • les actions, qui sont dans un objet dans la propriété actions dont les propriétés sont des fonctions qui vont appeler des mutation ;
  • les getters, qui sont dans un objet dans la propriété getters dont les propriétés sont des fonctions qui sont l'équivalent des computed des composants : elles vont calculer des données en fonctions d'autres données de l'état (ou même d'autres propriétés calculées) ;
  • enfin les modules qui sont dans un objet dans la propriété modules qui vont contenir des sous-stores.

# L'état (state)

export default createStore({
  state: {
    user: undefined
  },

  (...)
})

Ici, user est une propriété réactive de l'état (state) du store.

Dans tous les composants Vue, si le store est ajouté dans un composant parent (il est en général ajouté au composant App, composant parent de tous les autres), le store est accessible dans this.$store.

Dans un composant, user pourra donc être récupéré de cette façon :

<template>
  <div>
    Bienvenue, {{ user && user.login }}
  </div>
  <router-view/>
</template>

<script>
export default {
  name: 'AppHeader',

  computed: {
    user () {
      return this.$store.state.user // propriété `user` du `state` du `store`
    }
  }
}
</script>

Cela est tellement courant qu'une fonction utilitaire existe pour cela, mapState de vuex :

<template>
  <div>
    Bienvenue, {{ user && user.login }}
  </div>
  <router-view/>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'AppHeader',

  computed: mapState(['user']) // 
}
</script>

mapState retourne un objet avec une propriété user qui sera la propriété user du state du store.

# Les mutations

Les mutations servent à muter l'état. Pour changer la propriété user, par exemple, on peut créer ces mutations :

export default createStore({
  state: {
    user: undefined
  },

  mutations: {
    setUser (state, user) {
      state.user = user
    },

    resetUser (state) {
      state.user = undefined
    }
  },

  (...)
})

Il ne doit pas y avoir de logique dans les mutations, et il ne doit pas y avoir de code asynchrone. Elles doivent rester des fonctions très simples.

# Les actions

Les actions sont des fonctions dans lequel il peut y avoir de la logique et même du code asynchrone. Les actions vont appeler (on parle de commit) des mutations. Ce sont les composants, ou éventuellement le router, qui va appeler (on parle de dispatch) des actions.

export default createStore({
  state: {
    user: undefined
  },

  mutations: {
    setUser (state, user) {
      state.user = user
    },

    resetUser (state) {
      state.user = undefined
    }
  },

  actions: {
    login ({ commit }, credentials) {
      api.login(credentials)
        .then(data => {
          const { success, user, token, message } = data
          if (!success) {
            // TODO: Afficher proprement le message contenu dans `message` dans l'interface
            //       et non dans la console comme ici
            console.error(message)
            return
          }
          localStorage.setItem('token', token)
          commit('setUser', user)
        })
    },

    logout ({ commit }) {
      localStorage.removeItem('token')
      commit('resetUser')
    }
  },

  (...)
})

# Les getters

Les getters sont des fonctions que l'on utilise comme des propriétés, exactement comme les computed des composants.

export default createStore({
  state: {
    user: undefined
  },

  mutations: {
    setUser (state, user) {
      state.user = user
    },

    resetUser (state) {
      state.user = undefined
    }
  },

  actions: {
    login ({ commit }, credentials) {
      api.login(credentials)
        .then(data => {
          const { success, user, token, message } = data
          if (!success) {
            // TODO: Afficher proprement le message contenu dans `message` dans l'interface
            //       et non dans la console comme ici
            console.error(message)
            return
          }
          localStorage.setItem('token', token)
          commit('setUser', user)
        })
    },

    logout ({ commit }) {
      localStorage.removeItem('token')
      commit('resetUser')
    }
  },

  getters: {
    isLoggedIn (state) {
      return !!state.user
    }
  }
})

Ce getter isLoggedIn pourra donc s'utiliser comme suit dans un composant :

<template>
  <div>
    Bienvenue, {{ isLoggedIn && user.login }}
  </div>
  <router-view/>
</template>

<script>
export default {
  name: 'AppHeader',

  computed: {
    user () {
      return this.$store.state.user // propriété `user` du `state` du `store`
    },

    isLoggedIn () {
      return this.$store.getter.isLoggedIn
    }
  }
}
</script>

Il existe aussi une fonction mapGetters dans vuex qui permet d'accéder plus facilement aux getters. Attention, comme mapState et mapGetters sont des fonctions qui renvoient des objets, si on veut utiliser les deux, il faudra utiliser la syntaxe de décomposition :

<template>
  <div>
    Bienvenue, {{ isLoggedIn && user.login }}
  </div>
  <router-view/>
</template>

<script>
export default {
  name: 'AppHeader',

  computed: {
    ...mapState(['user']), // Décomposition de toutes les propriétés de l'objet retourné par mapState('user')
    ...mapGetters(['isLoggedIn']) // Décomposition de toutes les propriétés de l'objet retourné par mapGetters(['isLoggedIn'])
  }
}
</script>

# Le découpage en modules

Lorsque le store a trop de responsabilités, il convient de le diviser en sous-store, appelés modules dans la terminologie vuex.

Voici un exemple :

store/index.js

export default createStore({
  modules: {
    user
  }
})

store/user-module.js

export default {
  state: {
    data: undefined,
    isFetching: false
  },

  mutations: {
    setUser (state, user) {
      state.data = user
    },

    resetUser (state) {
      state.data = undefined
    }
  },

  actions: {
    login ({ commit }, credentials) {
      api.login(credentials)
        .then(data => {
          const { success, user, token, message } = data
          if (!success) {
            // TODO: Afficher proprement le message contenu dans `message` dans l'interface
            //       et non dans la console comme ici
            console.error(message)
            return
          }
          localStorage.setItem('token', token)
          commit('setUser', user)
        })
    },

    logout ({ commit }) {
      localStorage.removeItem('token')
      commit('resetUser')
    }
  },

  getters: {
    isLoggedIn (state) {
      return !!state.data
    }
  }
}

Désormais, les infos de l'utilisateur seront accessible comme ceci :

 <template>
   <div>
     Bienvenue, {{ isLoggedIn && user.login }}
   </div>
   <router-view/>
 </template>
 
 <script>
 export default {
   name: 'AppHeader',
 
   computed: {
     user () {
-       return this.$store.state.user // propriété `user` du `state` du `store`
+       return this.$store.state.user.data // propriété `user` du `state` du `store`
     },
 
     isLoggedIn () {
       return this.$store.getter.isLoggedIn
     }
   }
 }
</script>

ou bien :

 <template>
   <div>
-    Bienvenue, {{ isLoggedIn && user.login }}
+    Bienvenue, {{ isLoggedIn && user.data.login }}
   </div>
   <router-view/>
 </template>
 
 <script>
 export default {
   name: 'AppHeader',
 
   computed: {
     ...mapState(['user']),
     ...mapGetters(['isLoggedIn'])
   }
 }
 </script>

ou bien encore :

 <template>
   <div>
    Bienvenue, {{ isLoggedIn && user.login }}
   </div>
   <router-view/>
 </template>
 
 <script>
 export default {
   name: 'AppHeader',
 
   computed: {
-     ...mapState(['user']),
+     ...mapState({ // Ici, ce n'est plus un tableau (Array) mais un objet (Object litteral)
+       user: state => state.user.data // Chaque propriété est une fonction qui reçoit le state global en paramètre
+     }),
     ...mapGetters(['isLoggedIn'])
   }
 }
 </script>

# L'authentification

La méthode la plus utilisée aujourd'hui pour gérer l'authentification est par les JWT. Cf. cours du premier semestre.

# Garder les données utilisateurs dans le state après l'authentification

Il s'agit tout d'abord d'envoyer les identifiants (nom d'utilisateur et mot de passe) avec XHR ou fetch, de récupérer le JWT renvoyé par le serveur dans le corps de la réponse, et ensuite de le stocker dans le localStorage.

Le serveur devrait aussi renvoyer des infos concernant l'utilisateur. En général au moins son nom d'utilisateur, souvent en plus un identifiant (celui de la base de données, par exemple), et éventuellement sa date de naissance, ses préférences, etc.

# Regarder dans le localStorage et vérifier le token

Si jamais l'utilisateur s'est authentifié il y a peu, le JWT est sans doute encore valable. Il ne faudrait donc pas lui redemander de s'authentifier.

L'application devrait donc, au chargement, regarder dans le localStorage s'il y a JWT, et si c'est le cas, il faudrait vérifier sa validité.

# Rediriger le cas échéant (se souvenir du chemin demandé)

Si le JWT est toujours valide, l'utilisateur devrait être redirigé vers la home ou une page protégée (qui nécessite une authentification).

Exercice :

Si le JWT n'est pas valide et que l'utilisateur a demandé une page protégée :

  1. le chemin vers la page protégée devrait être mémorisée,
  2. l'utilisateur devrait être redirigé vers une page d'authentification,
  3. et si celle-ci réussit, il devrait être redirigé vers la page demandée initialement

# MongoDB Partie 1

### Qu'est-ce qu'une base de données orientée documents

# Se créer un compte gratuit sur Mongo atlas

# Tout avoir en local : Docker

# Docker

# Le concept de container

# Les images et les containers (conteneurs)

# Les commandes de la CLI docker

# Les fichiers de configuration des images

# Les images node.js

# Les images mongodb et mongo-express

# Docker-compose

# Les fichiers de configuration docker-compose

# Créer un docker-compose de dev et un de prod

# MongoDB partie 2

# Introduction à Mongoose

# Définition des schémas : exemple de user

# Le découpage schemas-queries-controllers

# Déployer sur Heroku

# Déployer le back sur Heroku et le front sur Netlify

# Automatiser le tout avec github actions