https://d226lax1qjow5r.cloudfront.net/blog/blogposts/build-a-serverless-eurovision-voting-system-with-node-js-and-vonage/Blog_Eurovision-Voting_1200x600.png

Construire un système de vote Eurovision sans serveur avec Node.js et Vonage

Publié le May 4, 2021

Temps de lecture : 11 minutes

L'Eurovision est l'un de mes événements préférés de l'année. Pour ceux qui ne le savent pas, l'Eurovision est un concours de chant qui est bizarre, merveilleux et loufoque à parts égales. Chaque pays participant soumet un numéro pour interpréter une chanson originale - qui est souvent ridicules et brillantes. Allez-y donc - avoir a quelques d'autres liens.

Les pays qui parviennent en finale se produisent en direct devant les citoyens des pays participants qui votent pour leur numéro préféré (à l'exclusion du leur). Les votes de chaque pays sont comptabilisés et donnent lieu à l'attribution de 58 points : 12 pour le meilleur, puis 10, et enfin 8 à 1 : 12 pour le meilleur, puis 10, et enfin 8 à 1. Ces dernières années, les jurys professionnels représentent la moitié des votes pour chaque pays, mais nous oublierons leur existence pour les besoins de ce projet.

2019 Eurovision Leaderboard2019 Eurovision Leaderboard

Je suis un grand fan de l'Eurovision et j'ai pensé que ce serait un projet amusant de construire un système de vote entièrement fonctionnel en utilisant l'API Number Insight de Vonage pour valider l'origine d'un numéro.

Nous commencerons par créer une base de données avec tous les pays participants. Cette base de données indiquera également qui sont les finalistes (en utilisant les candidats de 2019). Ensuite, nous traiterons les votes entrants par SMS, stockerons les votes s'ils sont valides et répondrons à l'aide de l Vonage Messages API. Enfin, nous construirons un front-end qui nous permettra d'obtenir les résultats par pays avec un classement actualisé. L'ensemble du projet sera hébergé sur Netlify avec Vue.js utilisé pour notre interface minimale.

Si vous souhaitez simplement voir le code fini, vous pouvez le trouver à l'adresse suivante https://github.com/nexmo-community/eurovision-voting-system-js.

Prêt ? Allons-y !

Conditions préalables

Nous allons avoir besoin de quelques Account pour que cela fonctionne. Si vous ne l'avez pas encore fait, obtenez un :

Ouvrez le terminal, créez un nouveau répertoire vide pour ce projet et initialisez un nouveau projet en tapant npm init -y. Une fois le projet terminé, installez les dépendances nécessaires en exécutant npm install dotenv encoding mongodb netlify-lambda nexmo@beta.

Vous aurez également besoin du CLI Nexmo. Exécutez npm install -g nexmo-cli@beta pour l'installer, allez sur votre Account en ligne pour obtenir votre clé/secret API, puis exécutez nexmo setup <api_key> <api_secret>.

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

Mise en place d'une base de données MongoDB

Nous utiliserons une instance MongoDB hébergée sur MongoDB Atlas. Connectez-vous à votre Account Mongo DB Atlas et créez un nouveau projet avec le nom que vous voulez. Créez un nouveau cluster (le niveau gratuit est parfait) - j'appelle le mien Eurovision-et attendez que les changements soient déployés.

Cliquez sur le bouton Connecter dans votre nouveau cluster, ajoutez votre adresse IP actuelle et créez un nouvel utilisateur MongoDB qui peut accéder à cette base de données (prenez note du mot de passe).

Dans le volet suivant, plusieurs possibilités de connexion à votre base de données s'offrent à vous. Choisissez Connecter votre application et copiez l'URI dans votre presse-papiers.

Créer un fichier .env

Avant de continuer, nous devons créer un nouveau fichier .env dans le répertoire du projet pour contenir toutes les chaînes sensibles que nous ne voulons pas que d'autres personnes voient. Le contenu du fichier doit être le suivant :

DB_URL=<Mongo DB URI>

Remplacez <password> par votre mot de passe d'utilisateur MongoDB, et <dbname> par eurovision.

Créer des collections

Cliquez sur l'onglet Collections dans votre cluster, puis Ajouter mes propres données pour créer une nouvelle collection. Nous devrions en créer deux :

  1. Nom de la base de données : eurovision, nom de la collection : countries

  2. Nom de la base de données : eurovision, nom de la collection : votes

Permettre l'accès de n'importe où

Nous avons ajouté notre propre adresse IP à la liste, ce qui permet d'accéder à cette base de données à partir de notre application locale. Cependant, lorsque nous déploierons ultérieurement ce projet, nous n'aurons pas accès aux adresses IP statiques. Cliquez sur Accès au réseau dans la barre latérale, puis Ajouter une adresse IPet enfin Autoriser l'accès depuis n'importe où. Confirmez vos modifications pour lever les restrictions.

Remplir avec des pays

En 2019, il y a eu 42 participations à l'Eurovision, dont 26 ont atteint la finale. Comme nous ne devons remplir ces données qu'une seule fois, j'ai écrit un script pour automatiser cette saisie. Créez un dossier appelé boilerplateet, à l'intérieur de celui-ci, un fichier appelé addCountries.js. Insérez le code suivant dans le fichier :

// Load environment variables
require('dotenv').config()
 // Initialize MongoClient
const { MongoClient } = require('mongodb')
const mongo = new MongoClient(process.env.DB_URL, { useUnifiedTopology: true })
 const countriesList = [
  { "iso": "ALB", "name": "Albania", "final": true },
  { "iso": "ARM", "name": "Armenia", "final": false },
  { "iso": "AUS", "name": "Australia", "final": true },
  { "iso": "AUT", "name": "Austria", "final": false },
  { "iso": "AZE", "name": "Azerbaijan", "final": true },
  { "iso": "BLR", "name": "Belarus", "final": true },
  { "iso": "BEL", "name": "Belgium", "final": false },
  { "iso": "HRV", "name": "Croatia", "final": false },
  { "iso": "CYP", "name": "Cyprus", "final": true },
  { "iso": "CZE", "name": "Czech Republic", "final": true },
  { "iso": "DNK", "name": "Denmark", "final": true },
  { "iso": "EST", "name": "Estonia", "final": true },
  { "iso": "FIN", "name": "Finland", "final": false },
  { "iso": "FRA", "name": "France", "final": true },
  { "iso": "DEU", "name": "Germany", "final": true },
  { "iso": "GEO", "name": "Georgia", "final": false },
  { "iso": "GRC", "name": "Greece", "final": true },
  { "iso": "HUN", "name": "Hungary", "final": false },
  { "iso": "ISL", "name": "Iceland", "final": true },
  { "iso": "IRL", "name": "Ireland", "final": false },
  { "iso": "ISR", "name": "Israel", "final": true },
  { "iso": "ITA", "name": "Italy", "final": true },
  { "iso": "LVA", "name": "Latvia", "final": false },
  { "iso": "LTU", "name": "Lithuania", "final": false },
  { "iso": "MKD", "name": "North Macedonia", "final": true },
  { "iso": "MLT", "name": "Malta", "final": true },
  { "iso": "MDA", "name": "Moldova", "final": false },
  { "iso": "MNE", "name": "Montenegro", "final": false },
  { "iso": "NLD", "name": "Netherlands", "final": true },
  { "iso": "NOR", "name": "Norway", "final": true },
  { "iso": "POL", "name": "Poland", "final": false },
  { "iso": "PRT", "name": "Portugal", "final": false },
  { "iso": "ROU", "name": "Romania", "final": false },
  { "iso": "RUS", "name": "Russia", "final": true },
  { "iso": "SMR", "name": "San Marino", "final": true },
  { "iso": "SRB", "name": "Serbia", "final": true },
  { "iso": "SVN", "name": "Slovenia", "final": true },
  { "iso": "ESP", "name": "Spain", "final": true },
  { "iso": "SWE", "name": "Sweden", "final": true },
  { "iso": "CHE", "name": "Switzerland", "final": true },
  { "iso": "UKR", "name": "Ukraine", "final": false },
  { "iso": "GBR", "name": "United Kingdom", "final": true }
]
 // Connect to database, and insert all items in the countryList in the countries collection
mongo.connect().then(async () => {
  try {
    const countries = await mongo.db('eurovision').collection('countries')
    const result = await countries.insertMany(countriesList)
    console.log(`Added ${result.insertedCount} documents to the collection`)
    mongo.close()
  } catch(e) {
    console.error(e)
  }
})

Enregistrez le fichier, ouvrez votre terminal et exécutez node boilerplate/addCountries.js. Une fois cette opération terminée, vérifiez votre collection dans MongoDB Atlas et vous devriez voir 42 documents dans la collection des pays.

Country entries populated in AtlasCountry entries populated in Atlas

Mise en place d'une fonction Netlify

Nous devons créer deux points de terminaison pour l'intégration de l'API Vonage. Le premier est un point de terminaison de statut qui, pour cette application, n'a besoin d'aucune logique mais doit renvoyer un statut HTTP 200. Pour construire et héberger ces points de terminaison, nous utiliserons Netlify Functions. Avant cela, nous devons procéder à quelques réglages.

Dans votre fichier package.json remplacez la section scripts par ce qui suit :

"scripts": {
  "netlify:serve": "netlify-lambda serve functions/src",
  "netlify:build": "netlify-lambda build functions/src"
},

Créez un fichier netlify.toml dans le répertoire racine de votre projet et écrivez le code suivant :

[build]
  functions = "./functions/build"

Enfin, créez un répertoire functions dans votre projet, et à l'intérieur de celui-ci, créez un répertoire src à l'intérieur de celui-ci. Toutes nos fonctions Netlify seront créées dans ce répertoire.

Dans le nouveau répertoire functions/src créer un status.js dans le nouveau répertoire. Dans celui-ci, créez la fonction :

const headers = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'Content-Type'
}
 exports.handler = async (event, context) => {
  try {
    return { headers, statusCode: 200, body: 'ok' }
  } catch(e) {
    console.error('Error', e)
    return { headers, statusCode: 500, body: 'Error: ' + e }
  }
}

Dans le terminal, exécutez npm run netlify:serve. Dans un autre terminal, essayez le nouveau point de terminaison en exécutant curl http://localhost:9000/status. Le terminal devrait afficher une réponse de ok.

Accepter les messages entrants

Nous aurons également besoin d'un point d'extrémité pour recevoir des données lorsque notre numéro virtuel long (LVN) reçoit un message. Copiez et collez le contenu de status.js dans un nouveau fichier appelé inbound.js.

Créer le point final entrant

Au début du fichier, demandez le paquet querystring (intégré à Node.js) :

const qs = require('querystring');

En haut du bloc try ajouter le code suivant :

const { msisdn, to: lvn, text } = qs.parse(event.body)
const vote = text.toUpperCase().trim()
console.log(vote)

Redémarrez le serveur netlify-lambda, ouvrez un nouveau terminal et exécutez npx ngrok http 9000 pour créer une version accessible au public de votre serveur netlify-lambda à des fins de test. Prenez note de l'URL temporaire de ngrok.

Mise en place d'une application API de Vonage

Dans le répertoire de votre projet, exécutez nexmo app:create:

  • Nom de l'Applications : tout ce que vous voulez

  • Sélectionner les capacités : messages

  • Messages URL entrant : <ngrok_url>/inbound

  • Messages Status URL : <ngrok_url>/status

  • Public/Privé : laisser en blanc

Cette opération crée un fichier .nexmo-app dans votre répertoire. Nous l'utiliserons plus tard, mais ne le partagez pas car il contient votre clé privée. Notez le nouvel identifiant de l'Applications qui s'affiche dans votre terminal (vous le trouverez également dans le fichier .nexmo-app plus tard).

Ensuite, nous devons acheter et lier un LVN à cette application. Exécuter :

nexmo number:search GB --sms

Copiez un numéro et exécutez-le :

nexmo number:buy <number>
nexmo link:app <number> <application_id>
nexmo numbers:update <number> --mo_http_url=<ngrok_url>/inbound

Le LVN est maintenant configuré et transmet les demandes à l'application. Essayez de lui envoyer un message et voyez-le apparaître dans votre terminal.

Message logged in terminalMessage logged in terminal

Ajoutez ce qui suit au .env pour plus tard :

VONAGE_KEY=<your_api_key>
VONAGE_SECRET=<your_api_secret>
VONAGE_APP=<your_application_id>
VONAGE_PRIVATE_KEY=<your_private_key>

Vous trouverez la clé privée de votre application dans le fichier .nexmo_app dans le fichier

Enregistrer le vote dans la base de données

Tout en haut de inbound.js, on exige et on initialise MongoClient:

require('dotenv').config()
const { MongoClient } = require('mongodb')
const mongo = new MongoClient(process.env.DB_URL, { useUnifiedTopology: true })

Sous l'instruction console.log(vote) se connecter à la base de données et ajouter une nouvelle entrée dans la collection pour tester son fonctionnement :

await mongo.connect()
const votes = await mongo.db('eurovision').collection('votes')
const countries = await mongo.db('eurovision').collection('countries')
 await votes.insertOne({ msisdn, lvn, vote })

Attendez que votre serveur netlify-lambda redémarre automatiquement et envoyez un autre message à votre LVN. Si vous consultez votre collection de votes dans Atlas, un nouveau document devrait apparaître.

Obtenir des informations sur les Numbers

L'API Number Insight de Vonage fournit des informations sur un numéro de téléphone (MSISDN). Il existe trois niveaux : basique, standard et avancé. Pour cette application, nous voulons connaître le pays d'origine d'un numéro, qui est renvoyé dans le cadre d'une recherche de base.

Juste au-dessus de l'endroit où les headers sont définies, il faut demander et initialiser la bibliothèque Nexmo node client :

const Nexmo = require('nexmo')
const nexmo = new Nexmo({
  apiKey: process.env.VONAGE_KEY,
  apiSecret: process.env.VONAGE_SECRET,
  applicationId: process.env.VONAGE_APP,
  privateKey: Buffer.from(process.env.VONAGE_PRIVATE_KEY.replace(/\\n/g, "\n"), 'utf-8')
})

Note : Nous devons créer un Buffer et remplacer \n pour que cette application fonctionne une fois hébergée sur Netlify. Dans les applications non hébergées sur Netlify, vous pouvez fournir ceci directement comme process.env.VONAGE_PRIVATE_KEY.

Tout en bas du fichier, créez une nouvelle fonction pour obtenir le code du pays à partir d'un numéro :

function getCountryCodeFromNumber(number) {
  return new Promise((resolve, reject) => {
    nexmo.numberInsight.get({level: 'basic', number}, async (err, res) => {
      if(err) reject(err)
      else resolve(res.country_code_iso3)
    })
  })
}

L'API Number Insight renvoie d'autres informations. Pour cette application, nous n'avons besoin que du code ISO à trois chiffres associé au numéro de téléphone. Ce code ISO est également stocké pour chaque pays participant à notre collection. countries collection.

Au-dessus de la mention votes.insertOne() ajouter :

const votersCountry = await getCountryCodeFromNumber(msisdn)
console.log(votersCountry)

Envoyez un autre message à votre LVN. Le code du pays doit être enregistré dans le terminal.

Envoyer une réponse à l'utilisateur

Lorsque nous recevons un message, nous devons répondre à l'utilisateur et l'en informer. Tout en bas de votre application, ajoutez une fonction à cet effet :

function sendMessage(sender, recipient, text) {
  return new Promise((resolve, reject) => {
    const to = { type: 'sms', number: recipient }
    const from = { type: 'sms', number: sender }
    const message = { content: { type: 'text', text } } 
    nexmo.channel.send(to, from, message, (err, res) => {
      if(err) reject(err)
      resolve({ headers, statusCode: 200, body: 'ok' })
    })
  })
}

Nous pouvons maintenant utiliser la fonction pour envoyer un message aux utilisateurs, puis renvoyer directement sa valeur. Remplacez l'instruction return dans le bloc try {} par notre nouvel appel de fonction :

return await sendMessage(lvn, msisdn, 'Thank you for voting!')

Envoyez un message à votre LVN et vous devriez recevoir une réponse.

Vérifier la validité du vote

Nous ne voulons pas stocker tous les votes qui nous sont envoyés. Certains contrôles sont nécessaires pour qu'ils soient valides. Sous la variable votersCountry créez les contrôles :

const existingVote = await votes.findOne({ msisdn: msisdn })
const countryInFinal = await countries.findOne({ iso: vote, final: true })
const votersCountryCanVote = await countries.findOne({ iso: votersCountry })
 if(existingVote) {
  return await sendMessage(lvn, msisdn, 'You have already voted')
}
if(!countryInFinal) {
  return await sendMessage(lvn, msisdn, 'That country is not in the final, or your message is not a valid country code.')
}
if(!votersCountryCanVote) {
  return await sendMessage(lvn, msisdn, 'Your number is not from a participating country')
}
if(votersCountry == vote) {
  return await sendMessage(lvn, msisdn, 'You cannot vote for your own country')
}

Modifier l'objet à l'intérieur de votes.insertOne() pour y inclure les informations que nous voulons stocker :

votes.insertOne({ msisdn, vote, votersCountry })

Comme les instructions "if" contiennent des instructions de retour, le vote ne sera inséré que si aucune des conditions n'est remplie, ce qui signifie qu'il est valide.

Remplir avec des votes

Notre système de vote est maintenant complet. Cependant, pour construire des terminaux de résultats, nous aurons besoin de milliers de votes. Comme précédemment, voici un script qui ajoutera 20k votes. Ajoutez ce code dans un nouveau fichier addVotes.js dans le répertoire boilerplate :

require('dotenv').config()
const { MongoClient } = require('mongodb')
const mongo = new MongoClient(process.env.DB_URL, { useUnifiedTopology: true })
 mongo.connect().then(async () => {
  try {
    const countries = await mongo.db('eurovision').collection('countries')
    const votes = await mongo.db('eurovision').collection('votes')
    const list = await countries.find().toArray()
     const votesList = []
    for(let i=0; i<20000; i++) {
      const { iso: votersCountry } = list[Math.floor(Math.random() * list.length)]
      const availableCountries = list.filter(c => c != votersCountry && c.final)
      const { iso: vote } = availableCountries[Math.floor(Math.random() * availableCountries.length)]
       votesList.push({
        msisdn: String(Math.ceil(Math.random() * 100000)),
        votersCountry, vote
      })
    }
    
    const result = await votes.insertMany(votesList)
    console.log(`Added ${result.insertedCount} documents to the collection`)
    mongo.close()
  } catch(e) {
    console.error(e)
  }
})

Supprimez vos documents existants, puis exécutez ce script 5 ou 6 fois. Votre base de données MongoDB Atlas devrait maintenant contenir un grand nombre d'échantillons de votes.

12000 records in the votes collection12000 records in the votes collection

Créer des points de terminaison pour le front-end

Nous avons besoin d'un point de terminaison pour renvoyer les pays afin d'alimenter la liste déroulante, et d'un point de terminaison pour renvoyer les scores d'un pays donné.

Completed dashboardCompleted dashboard

Obtenir la liste des pays

Créer un nouveau fichier dans /functions/src/countries.js:

require('dotenv').config()
const { MongoClient } = require('mongodb')
const mongo = new MongoClient(process.env.DB_URL, { useUnifiedTopology: true })
 const headers = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'Content-Type'
}
 exports.handler = async (event, context) => {
  try {
    await mongo.connect()
    const countries = await mongo.db('eurovision').collection('countries')
    const list = await countries.find().toArray()
    return { headers, statusCode: 200, body: JSON.stringify(list) }
  } catch(e) {
    console.error('Error', e)
    return { headers, statusCode: 500, body: 'Error: ' + e }
  }
}

Redémarrez votre serveur netlify-lambda et essayez-le en lançant curl http://localhost:9000/countries.

Obtenir des résultats

Ce point d'accès acceptera un paramètre d'interrogation de type ?country=CODE. Copiez et collez le code du point de terminaison des pays dans un nouveau fichier appelé results.js. Remplacez le contenu du bloc try {} par ce qui suit :

await mongo.connect()
const countries = await mongo.db('eurovision').collection('countries')
const votes = await mongo.db('eurovision').collection('votes')
 const { country } = event.queryStringParameters
 const topTen = await votes.aggregate([
  { $match: { votersCountry: country } },
  { $group: { _id: '$vote', votes: { $sum: 1 } } },
  { $sort: { votes: -1 } },
  { $limit: 10 }
]).toArray()
 const points = [ 12, 10, 8, 7, 6, 5, 4, 3, 2, 1 ]
 const list = await countries.find().toArray()
 const results = topTen.map((votes, i) => {
  const countryRecord = list.find(c => c.iso == votes._id)
  return {
    ...votes,
    points: points[i],
    country: countryRecord.name
  }
})
 return { headers, statusCode: 200, body: JSON.stringify(results) }

La variable topTen utilise une agrégation MongoDB pour renvoyer les 10 meilleures entrées telles qu'elles ont été votées par le pays fourni. Nous ajoutons ensuite une valeur de points à chacune des entrées avec leur valeur de points donnée dans le tableau points tableau.

Redémarrez le serveur et exécutez curl http://localhost:9000/results?country=GBR pour tester.

Scaffold Front-end

Créer un nouveau fichier à la racine du projet appelé index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Eurovision Results Pane</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div id="app">
    <div id="leaderboard">
      <h1>Leaderboard</h1>
      <div class="list">
        <div class="country" v-for="country in leaderboard">
          <span class="name">{{country.name}}</span>
          <span class="score">{{country.score}}</span>
        </div>
      </div>
    </div>
    <div id="admin">
      <h1>Get Results</h1>
      <form>
        <select v-model="toReveal">
          <option disabled value="">Select country</option>
          <option v-for="country in leftToReveal" :value="country.iso">{{country.name}}</option>
        </select>
        <input type="submit" @click.prevent="getScores" value="Get Scores">
      </form>
      <div id="results">
        <h2>{{resultsCountry}}</h2>
        <div class="result" v-for="result in results">
          <span class="name">{{result.country}}</span>
          <span class="points">+{{result.points}}</span>
        </div>
      </div>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="client.js"></script>
</body>
</html>

Créer un fichier style.css à la racine du projet :

@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;900&display=swap');

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    background: #050636;
    font-family: 'Montserrat', sans-serif;
}

#app {
    display: grid;
    grid-template-columns: auto 350px;
    grid-gap: 1em;
    padding: 1em;
}

#leaderboard {
    background: white;
    color: #050636;
    padding: 1em 1em 0;
}

.list {
    columns: 2;
    column-gap: 1em;
    margin-top: 1em;
}

.country,
.result {
    padding: 0.5em;
    background: #f0f0f0;
    margin-bottom: 1em;
    width: 100%;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    font-size: 1.25em;
    align-items: center;
}

.score {
    font-size: 1.25em;
    font-weight: bold;
}

#admin {
    background: #2a2b87;
    color: white;
    padding: 1em;
}

form {
    display: grid;
    grid-template-columns: 225px auto;
    grid-gap: 1em;
}

form {
    margin: 1em 0;
}

.result {
    background: #4c4eb3;
    margin-top: 0.5em;
}

Créer un fichier client.js à la racine du projet :

const app = new Vue({
  el: '#app',
  async created() {
    const countryResp = await fetch(this.baseURL + '/countries');
    const countries = await countryResp.json();
    this.countries = countries.map(country => {
      return { ...country, results: false, score: 0 }
    })
  },
  data: {
    countries: [],
    toReveal: undefined,
    results: undefined,
    resultsCountry: undefined
  },
  computed: {
    leaderboard() {
      return this.countries.filter(c => c.final).sort((a, b) => b.score - a.score)
    },
    leftToReveal() {
      return this.countries.filter(c => !c.results)
    },
    baseURL() {
      return "http://localhost:9000"
    },
    toRevealCountry() {
      const country = this.countries.find(c => c.iso == this.toReveal)
      return country.name
    }
  },
  methods: {
    async getScores() {
      // Get results
      const resultsResp = await fetch(this.baseURL + '/results?country=' + this.toReveal);
      this.results = await resultsResp.json();
       // Assign points to countries
      for(let result of this.results) {
        const country = this.countries.find(c => c.iso == result._id)
        country.score += result.points
      }
       // Remove item from results select
      const votingCountry = this.countries.find(c => c.iso == this.toReveal)
      votingCountry.results = true
      
      // Show country name in results pane
      this.resultsCountry = votingCountry.name
    }
  }
})

Quelques points essentiels à noter :

  • En created() nous ajoutons deux propriétés à chaque pays : un score initial de 0 et une propriété results à laquelle nous attribuons la valeur "true" une fois que nous avons obtenu des résultats pour ce pays.

  • La propriété leftToReveal n'inclut que les pays qui ont results a pour valeur trueafin d'éviter qu'un pays ne soit accidentellement compté deux fois.

Résultats persistants entre les rafraîchissements

Il s'agit d'un système assez bon et assez robuste. Il est possible de l'améliorer en conservant les scores entre les actualisations (si cela se produit lors de la présentation des résultats).

Au bas de la méthode getScores() ajoutez la ligne countries dans localStorage :

localStorage.setItem('countries', JSON.stringify(this.countries))

Mise à jour created() pour ne récupérer les nouvelles données sur les pays que si nous n'en avons pas dans localStorage :

async created() {
  if(localStorage.getItem('countries')) {
    this.countries = JSON.parse(localStorage.getItem('countries')) 
  } else {
    const countryResp = await fetch(this.baseURL + '/countries');
    const countries = await countryResp.json();
    this.countries = countries.map(country => {
      return { ...country, results: false, score: 0 }
    })
  }
},

Héberger sur Netlify

Créez un nouveau fichier à la racine de votre projet, appelé.gitignore. Les fichiers et répertoires listés dans ce fichier ne seront pas inclus dans un dépôt git. Votre fichier devrait ressembler à ceci :

node_modules
functions/build
.env
.nexmo-app

Poussez ce dépôt sur GitHub puis connectez-vous à votre compte Netlify. Cliquez sur Nouveau site depuis Gitsélectionnez le dépôt et dans le champ Paramètres de construction de base la commande Build doit être npm run netlify:build. Dans les Paramètres de construction avancés ajoutez chaque élément dans votre fichier .env dans votre fichier

Une fois le projet déployé, vous devrez procéder à deux changements :

  1. Mettez à jour vos URL dans votre Application API de Vonage à <netlify_url>/.netlify/functions/status (ou /inbound).

  2. Dans le cadre de client.js mettez à jour votre baseURL par la méthode suivante :

baseURL() {
  if(location.hostname == 'localhost' || location.hostname == "127.0.0.1") {
    return "http://localhost:9000"
  }  else {
    return "<netlify_url>/.netlify/functions"
  }
},

Poussez un nouveau commit et votre site Netlify se redéploiera automatiquement.

Synthèse et prochaines étapes

Cette application comporte un certain nombre de pièces mobiles. Cependant, chaque partie fait son travail pour créer un système de vote Eurovision qui fonctionne réellement.

Vous pouvez obtenir plusieurs LVN de différents pays à l'aide de l'interface de commande Nexmo ou du tableau de bord Web. Les utilisateurs ne pourront toujours voter qu'une seule fois, quel que soit le LVN qu'ils envoient. Une amélioration que vous pourriez souhaiter apporter est de fermer la fenêtre de vote afin que tous les pays disposent de la même période pour voter.

Vous trouverez le projet final à l'adresse suivante https://github.com/nexmo-community/eurovision-voting-system-js

Comme toujours, si vous avez besoin d'aide, n'hésitez pas à nous contacter dans le Slack de la communauté des développeurs de Vonage. Communauté des développeurs de Vonage Slack. Nous espérons vous y voir.

L'Islande a d'ailleurs eu la meilleure entrée en 2020.

Partager:

https://a.storyblok.com/f/270183/400x400/c822f15b89/kevinlewis.png
Kevin LewisAnciens de Vonage

Ancien Developer Advocate pour Vonage, où son rôle était de soutenir la communauté technologique locale à Londres. Il est un organisateur d'événements expérimenté, un joueur de jeux de société et le père d'un adorable petit chien appelé Moo. Il est également l'organisateur principal de You Got This - un réseau d'événements sur les compétences de base nécessaires pour une vie professionnelle heureuse et saine.