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

Construye un sistema de votación de Eurovisión sin servidor con Node.js y Vonage

Publicado el May 4, 2021

Tiempo de lectura: 10 minutos

Eurovisión es uno de mis acontecimientos favoritos del año. Para los que no lo sepan, Eurovisión es un concurso de canto que es raro, maravilloso y chiflado a partes iguales. Cada país participante presenta un acto para interpretar una canción original - que a menudo son ridículas y brillantes. Vamos entonces - tener a pocos más enlaces.

Los países que llegan a la final actúan en directo ante los ciudadanos de los países participantes, que votan por su actuación favorita (sin incluir la suya propia). Se cuentan los votos de cada país y, como resultado, cada uno reparte 58 puntos: 12 para el primero, luego 10 y, por último, de 8 a 1. En los últimos años, los jurados profesionales representan la mitad de los votos de cada país, pero olvidaremos que existen por el bien de este proyecto.

2019 Eurovision Leaderboard2019 Eurovision Leaderboard

Soy un gran aficionado a Eurovisión y pensé que sería un proyecto divertido crear un sistema de votación totalmente funcional que utilizara la API Number Insight de Vonage para validar el origen de un número.

Primero crearemos una base de datos con todos los países participantes. Este conjunto de datos también destacará quiénes son los finalistas (utilizando los concursantes de 2019). A continuación, gestionaremos los votos entrantes a través de SMS, almacenaremos los votos si son válidos y responderemos utilizando la API de Messages de Vonage. Por último, construiremos un front-end que nos permitirá obtener resultados por país con una tabla de clasificación actualizada. Todo el proyecto estará alojado en Netlify con Vue.js utilizado para nuestro front-end mínimo.

Si sólo quieres ver el código terminado puedes encontrarlo en https://github.com/nexmo-community/eurovision-voting-system-js.

¿Preparados? ¡Venga!

Requisitos previos

Vamos a necesitar algunas Account para que esto funcione. Si aún no lo has hecho, consigue una:

Abra el terminal, cree un nuevo directorio vacío para este proyecto e inicialice un nuevo proyecto escribiendo npm init -y. Una vez completado, instale las dependencias necesarias ejecutando npm install dotenv encoding mongodb netlify-lambda nexmo@beta.

También necesitarás el Nexmo CLI. Ejecute npm install -g nexmo-cli@beta para instalarla, ve a tu Account online para obtener tu API Key/Secret, y luego ejecuta 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.

Configurar una base de datos MongoDB

Usaremos una instancia de MongoDB alojada en MongoDB Atlas. Accede a tu cuenta de Mongo DB Atlas y crea un nuevo proyecto con el nombre que quieras. Crea un nuevo cluster (el nivel gratuito está bien)-yo estoy llamando al mío Eurovision-y espera a que se desplieguen los cambios.

Haga clic en el botón Conectar en tu nuevo cluster, añade tu dirección IP actual, y crea un nuevo usuario MongoDB que pueda acceder a esta base de datos (toma nota de la contraseña).

En el siguiente panel, se nos presentan varias formas de conectarnos a la base de datos. Elija Conecte su aplicación y copie el URI en el portapapeles.

Crear archivo .env

Antes de continuar, debemos crear un nuevo archivo .env en el directorio del proyecto para contener todas nuestras cadenas sensibles que no queremos que otros vean. El contenido del archivo debe ser:

DB_URL=<Mongo DB URI>

Sustituya <password> con su contraseña de usuario de MongoDB, y <dbname> con eurovision.

Crear colecciones

Haga clic en Colecciones en su clúster y, a continuación Añadir mis propios datos para crear una nueva colección. Deberíamos crear dos:

  1. Nombre de la base de datos: eurovisionNombre de la colección: countries

  2. Nombre de la base de datos: eurovisionNombre de la colección: votes

Permitir el acceso desde cualquier lugar

Añadimos nuestra propia dirección IP a la lista, lo que permite el acceso a esta base de datos desde nuestra aplicación local. Sin embargo, cuando más tarde despleguemos este proyecto, no tendremos acceso a las direcciones IP estáticas. Haga clic en Acceso a la red en la barra lateral y, a continuación Añadir dirección IPy, por último Permitir acceso desde cualquier lugar. Confirme sus cambios para levantar las restricciones serán levantadas.

Poblar con países

En 2019, hubo 42 candidaturas a Eurovisión, de las cuales 26 llegaron a la final. Como solo tenemos que rellenar estos datos una vez, he escrito un script para automatizar esta entrada de datos. Crea una carpeta llamada boilerplatey dentro de ella un archivo llamado addCountries.js. Pon el siguiente código en el archivo:

// 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)
  }
})

Guarde el archivo, abra el terminal y ejecute node boilerplate/addCountries.js. Una vez completado, comprueba tu colección en MongoDB Atlas y deberías ver 42 documentos en la colección de países.

Country entries populated in AtlasCountry entries populated in Atlas

Configurar una función de Netlify

Hay dos puntos finales que debemos crear para la integración con la API de Vonage. El primero es un punto final de estado que, para esta aplicación, no necesita ninguna lógica pero debe devolver un estado HTTP 200. Para crear y alojar estos puntos finales, utilizaremos Netlify Functions. Antes de hacerlo, se requieren algunas configuraciones.

En su package.json sustituya la sección scripts por lo siguiente:

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

Cree un archivo netlify.toml en el directorio raíz de tu proyecto y escribe el siguiente código:

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

Por último, cree un directorio functions directorio en tu proyecto, y dentro de él crea un directorio src directorio. Todas nuestras Funciones Netlify serán creadas en este directorio.

En el nuevo functions/src cree un archivo status.js archivo. En él, crear la función:

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 }
  }
}

En el terminal ejecute npm run netlify:serve. En otro terminal, prueba el nuevo endpoint ejecutando curl http://localhost:9000/status. El terminal debería mostrar una respuesta de ok.

Aceptar mensajes entrantes

También necesitaremos un endpoint para recibir datos cuando se envíe un mensaje a nuestro número virtual largo (LVN). Copie y pegue el contenido de status.js en un nuevo archivo llamado inbound.js.

Crear el punto final de entrada

En la parte superior del archivo, requiere el paquete querystring (integrado en Node.js):

const qs = require('querystring');

En la parte superior del bloque try añada el siguiente código:

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

Reinicie el servidor netlify-lambda, abra un nuevo terminal y ejecute npx ngrok http 9000 para crear una versión de acceso público de su servidor netlify-lambda para pruebas. Tome nota de la URL ngrok temporal.

Configurar una aplicación API de Vonage

En el directorio del proyecto, ejecute nexmo app:create:

  • Application Name: lo que quieras

  • Seleccionar funciones: mensajes

  • Mensajes URL de entrada: <ngrok_url>/inbound

  • Mensajes Estado URL: <ngrok_url>/status

  • Público/Privado: dejar en blanco

Esta operación crea un archivo .nexmo-app en tu directorio. Lo usaremos más tarde, pero no lo compartas ya que contiene tu clave privada. Tome nota del nuevo ID de la Aplicación que aparece en su terminal (también puede encontrarlo en el archivo .nexmo-app más adelante).

A continuación, tenemos que comprar y vincular un LVN con esta aplicación. Corre:

nexmo number:search GB --sms

Copia un número y ejecútalo:

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

Ahora la LVN está configurada y reenviando peticiones a la aplicación. Prueba a enviarle un mensaje y verás cómo aparece en tu terminal.

Message logged in terminalMessage logged in terminal

Añade lo siguiente al .env para más tarde:

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

Puede encontrar la clave privada de su aplicación en el archivo .nexmo_app archivo.

Almacenar el voto en la base de datos

En la parte superior de inbound.jsrequiere e inicializa MongoClient:

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

Debajo de la sentencia console.log(vote) conéctate a la base de datos e introduce una nueva entrada en la colección para comprobar que funciona:

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 })

Espera a que tu servidor netlify-lambda se reinicie automáticamente y envía otro mensaje a tu LVN. Si compruebas tu colección de votos en Atlas, debería aparecer un nuevo documento.

Obtenga información sobre los Numbers

La API Number Insight de Vonage, dado un número de teléfono (MSISDN), brindará información sobre el mismo. Hay tres niveles: básico, estándar y avanzado. Para esta aplicación, queremos saber el país de origen de un número, que se devuelve como parte de una búsqueda básica.

Justo encima de donde se define el headers requiere e inicializa la librería del cliente de nodo Nexmo:

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')
})

Nota: Debemos crear un Buffer y reemplazar \n para que esta aplicación funcione una vez alojada en Netlify. En aplicaciones no alojadas en Netlify, puede proporcionar esto directamente como process.env.VONAGE_PRIVATE_KEY.

En la parte inferior del archivo, crea una nueva función para obtener el código de país de un número:

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)
    })
  })
}

Existen otros datos que devolverá la API Number Insight. Para esta aplicación, sólo necesitamos el código ISO de 3 dígitos asociado al número de teléfono. Este código ISO también se almacena para cada país participante en nuestra countries colección.

Encima de la votes.insertOne() declaración añada:

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

Envíe otro mensaje a su LVN. El código de país debe estar registrado en el terminal.

Enviar una respuesta al usuario

Cuando recibimos un mensaje, debemos responder al usuario y hacérselo saber. En la parte inferior de tu aplicación añade una función para hacer esto:

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' })
    })
  })
}

Ahora podemos utilizar la función para enviar un mensaje a los usuarios, y luego devolver su valor directamente. Sustituya la sentencia return del bloque try {} por nuestra nueva llamada a la función:

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

Envíe un mensaje a su LVN y debería recibir una respuesta.

Comprobar si el voto es válido

No queremos almacenar todos los votos que se nos envían. Hay algunas comprobaciones que son necesarias para que sea válido. Debajo de la variable votersCountry creamos las comprobaciones:

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')
}

Cambia el objeto dentro de votes.insertOne() para incluir la información que queremos almacenar:

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

Como hay sentencias return en las sentencias if, el voto sólo se insertará si no se cumple ninguna de las condiciones, lo que significa que es válido.

Poblar con votos

Nuestro sistema de votación ya está completo. Sin embargo, para construir puntos finales de resultados, necesitaremos miles de votos. Como antes, aquí hay un script que añadirá 20k votos. Añade este código en un nuevo archivo addVotes.js en el directorio 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)
  }
})

Elimine los documentos existentes y ejecute este script 5 o 6 veces. Tu base de datos MongoDB Atlas debería tener ahora un montón de votos de muestra.

12000 records in the votes collection12000 records in the votes collection

Crear puntos finales para el front-end

Hay algunas partes móviles en nuestro front-end: necesitamos un endpoint para devolver países con los que rellenar el desplegable, y un endpoint para devolver las puntuaciones de un país determinado.

Completed dashboardCompleted dashboard

Obtener la lista de países

Crear un nuevo archivo en /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 }
  }
}

Reinicie su servidor netlify-lambda y luego pruébelo ejecutando curl http://localhost:9000/countries.

Obtener resultados

Este punto final aceptará un parámetro de consulta de ?country=CODE. Copie y pegue el código del endpoint de los países en un nuevo archivo llamado results.js. Sustituya el contenido del bloque try {} por lo siguiente:

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 utiliza una agregación de MongoDB para devolver las 10 entradas más votadas por el país indicado. A continuación, añadimos un valor en puntos a cada una de las entradas con su valor en puntos dado en la matriz points matriz.

Reinicie el servidor y ejecute curl http://localhost:9000/results?country=GBR para probar.

Andamio Front-end

Crear un nuevo archivo en la raíz del proyecto llamado 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>

Cree un style.css archivo en la raíz del proyecto:

@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;
}

Cree un client.js archivo en la raíz del proyecto:

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
    }
  }
})

Algunas cosas clave a tener en cuenta:

  • En created() añadimos dos propiedades a cada país: una puntuación inicial de 0 y una propiedad results que establecemos en true una vez que tenemos resultados para ese país.

  • La propiedad leftToReveal sólo incluye los países que han results establecido en truepor lo que no podemos contabilizar accidentalmente un país por partida doble.

Resultados persistentes entre actualizaciones

Se trata de un sistema bastante bueno y sólido. Un aspecto que podemos mejorar es la persistencia de las puntuaciones entre actualizaciones (en caso de que esto ocurra mientras se presentan los resultados).

Al final del método getScores() añada los datos countries datos a localStorage:

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

Actualizar created() para obtener los datos del país sólo si no tenemos ninguno en 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 }
    })
  }
},

Anfitrión en Netlify

Cree un nuevo archivo en la raíz del proyecto llamado.gitignore. Los archivos y directorios listados en este archivo no se incluirán en un repositorio git. Tu archivo debería tener este aspecto:

node_modules
functions/build
.env
.nexmo-app

Empuja este repositorio a GitHub y luego inicia sesión en tu Account de Netlify. Haga clic en Nuevo sitio desde Gitseleccione el repositorio y en la ventana Configuración básica de construcción el comando Build debe ser npm run netlify:build. En los Configuración avanzada añada cada elemento de su archivo .env archivo.

Una vez desplegado, hay dos cambios que tendrás que hacer:

  1. Actualiza tus URL en tu aplicación API de Vonage a <netlify_url>/.netlify/functions/status (o /inbound).

  2. En client.js actualice su baseURL a lo siguiente:

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

Envíe un nuevo commit y su sitio Netlify se redistribuirá automáticamente.

Conclusión y próximos pasos

Hay bastantes partes móviles en esta aplicación. Sin embargo, cada parte hace su trabajo para crear un sistema de votación de Eurovisión que realmente funciona.

Puede obtener múltiples LVNs de diferentes países utilizando el Nexmo CLI o a través del panel web. Los usuarios sólo podrán votar una vez, independientemente del LVN que envíen. Una mejora que puede hacer es cerrar la ventana de votación para que todos los países tengan el mismo periodo para votar.

Puede encontrar el proyecto final en https://github.com/nexmo-community/eurovision-voting-system-js

Como siempre, si necesitas ayuda, no dudes en ponerte en contacto con nosotros en la Slack de la comunidad de desarrolladores de Vonage. Esperamos verte allí.

Islandia tuvo la mejor entrada de 2020, por cierto.

Compartir:

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

Antiguo defensor de los desarrolladores de Vonage, donde su función era apoyar a la comunidad tecnológica local de Londres. Es un experimentado organizador de eventos, jugador de mesa y padre de un precioso perrito llamado Moo. También es el principal organizador de You Got This, una red de eventos sobre las habilidades básicas necesarias para una vida laboral feliz y saludable.