https://a.storyblok.com/f/270183/81667/506bc09241/blog_eurovision-voting_1200x600.png

Crie um sistema de votação sem servidor para o Eurovision com Node.js e Vonage

Publicado em May 4, 2021

Tempo de leitura: 10 minutos

O Eurovision é um dos meus eventos favoritos do ano. Para quem não sabe, o Eurovision é um concurso de canto que é estranho, maravilhoso e excêntrico em partes iguais. Cada país participante indica um artista para apresentar uma música original — que muitas vezes são ridículas e geniais. Vamos lá, então — uma mais links links.

Os países que chegam à final se apresentam ao vivo, e o público dos países participantes vota em sua apresentação favorita (excluindo a do próprio país). Os votos de cada país são contados e, como resultado, cada um atribui 58 pontos: 12 para o primeiro colocado, depois 10 e, finalmente, de 8 a 1. Nos últimos anos, júris profissionais representam metade dos votos de cada país, mas vamos ignorar a existência deles para fins deste projeto.

2019 Eurovision Leaderboard2019 Eurovision Leaderboard

Sou um grande fã do Eurovision e achei que seria um projeto divertido criar um sistema de votação totalmente funcional usando a Number Insight API da Vonage para validar a origem de um número.

Primeiramente, vamos criar um banco de dados com todos os países participantes. Esse conjunto de dados também indicará quem são os finalistas (com base nos participantes de 2019). Em seguida, vamos processar os votos recebidos por SMS, armazenar os votos válidos e responder usando a Messages API da Vonage. Por fim, criaremos um front-end que nos permitirá obter os resultados por país com um quadro de classificação atualizado. Todo o projeto será hospedado no Netlify, utilizando o Vue.js para nosso front-end minimalista.

Se você quiser apenas ver o código finalizado, pode encontrá-lo em https://github.com/nexmo-community/eurovision-voting-system-js.

Pronto? Vamos lá!

Pré-requisitos

Vamos precisar de algumas contas para que isso funcione. Se ainda não tiver feito isso, crie um Account:

Abra o terminal, crie um novo diretório vazio para este projeto e inicialize um novo projeto digitando npm init -y. Quando terminar, instale as dependências necessárias executando npm install dotenv encoding mongodb netlify-lambda nexmo@beta.

Você também precisará da CLI do Nexmo. Para npm install -g nexmo-cli@beta para instalá-la, acesse seu Account online para obter sua chave e segredo de API e, em seguida, execute nexmo setup <api_key> <api_secret>.

Configurar um banco de dados MongoDB

Usaremos uma instância hospedada do MongoDB no MongoDB Atlas. Faça login na sua conta do MongoDB Atlas e crie um novo projeto com o nome que desejar. Crie um novo cluster (o plano gratuito já é suficiente) — vou chamar o meu de Eurovision— e aguarde até que as alterações sejam implantadas.

Clique no botão “Conectar” no seu novo cluster, adicione seu endereço IP atual e crie um novo usuário do MongoDB com permissão para acessar esse banco de dados (anote a senha).

No painel seguinte, são apresentadas várias opções para se conectar ao seu banco de dados. Escolha Conectar seu aplicativo e copie a URI para a área de transferência.

Criar arquivo .env

Antes de continuarmos, precisamos criar um novo .env arquivo no diretório do projeto para conter todas as nossas strings confidenciais que não queremos que outras pessoas vejam. O conteúdo do arquivo deve ser:

DB_URL=<Mongo DB URI>

Substitua <password> pela senha do seu usuário do MongoDB e <dbname> por eurovision.

Criar coleções

Clique nas botão “Coleções” no seu cluster e, em seguida, Adicionar meus próprios dados para criar uma nova coleção. Devemos criar duas:

  1. Nome do banco de dados: eurovision, nome da coleção: countries

  2. Nome do banco de dados: eurovision, nome da coleção: votes

Permitir acesso de qualquer lugar

Adicionamos nosso próprio endereço IP à lista, o que permite o acesso a esse banco de dados a partir do nosso aplicativo local. No entanto, quando, posteriormente, implantarmos esse projeto, não teremos acesso a endereços IP estáticos. Clique em Acesso à Rede na barra lateral e, em seguida, Adicionar endereço IPe, por fim, Permitir acesso de qualquer lugar. Confirme as alterações para que as restrições sejam removidas.

Preencher com países

Em 2019, houve 42 inscrições no Eurovision, das quais 26 passaram para a final. Como precisamos inserir esses dados apenas uma vez, criei um script para automatizar essa inserção. Crie uma pasta chamada boilerplate, e dentro dela um arquivo chamado addCountries.js. Coloque o seguinte código no arquivo:

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

Salve o arquivo, abra o terminal e execute node boilerplate/addCountries.js. Quando terminar, verifique sua coleção no MongoDB Atlas e você deverá ver 42 documentos na coleção “countries”.

Country entries populated in AtlasCountry entries populated in Atlas

Configurar uma função do Netlify

Precisamos criar dois endpoints para a integração com a API da Vonage. O primeiro é um endpoint de status que, para esta aplicação, não requer nenhuma lógica, mas deve retornar um status HTTP 200. Para criar e hospedar esses endpoints, usaremos o Netlify Functions. Antes disso, é necessário realizar algumas configurações.

No seu package.json arquivo, substitua a scripts seção pelo seguinte:

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

Crie um netlify.toml arquivo no diretório raiz do seu projeto e escreva o seguinte código:

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

Por fim, crie um functions diretório no seu projeto e, dentro dele, crie um src diretório. Todas as nossas funções do Netlify serão criadas nesse diretório.

No novo functions/src diretório, crie um status.js arquivo. Nele, crie a função:

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

No terminal, execute npm run netlify:serve. Em outro terminal, teste o novo endpoint executando curl http://localhost:9000/status. O terminal deve exibir uma resposta de ok.

Aceitar mensagens recebidas

Também precisaremos de um endpoint para receber dados quando for enviada uma mensagem para o nosso Número Virtual Longo (LVN). Copie e cole o conteúdo de status.js em um novo arquivo chamado inbound.js.

Criar o ponto de extremidade de entrada

No início do arquivo, inclua o pacote `querystring` (integrado ao Node.js):

const qs = require('querystring');

No início do try bloco, adicione o seguinte código:

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

Reinicie o servidor netlify-lambda, abra um novo terminal e execute npx ngrok http 9000 para criar uma versão acessível ao público do seu servidor netlify-lambda para testes. Anote a URL temporária do ngrok.

Configurar uma aplicação da API da Vonage

No diretório do seu projeto, execute nexmo app:create:

  • Nome do aplicativo: o que você quiser

  • Recursos selecionados: mensagens

  • URL de entrada de mensagens: <ngrok_url>/inbound

  • URL do status das mensagens: <ngrok_url>/status

  • Público/Privado: deixe em branco

Essa operação cria um .nexmo-app arquivo no seu diretório. Vamos usá-lo mais tarde, mas não o compartilhe, pois ele contém sua chave privada. Anote o novo ID da aplicação exibido no seu terminal (você também pode encontrá-lo no .nexmo-app arquivo mais tarde).

Em seguida, precisamos adquirir e vincular um LVN a este aplicativo. Execute:

nexmo number:search GB --sms

Copie um número e, em seguida, execute:

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

Agora que o LVN está configurado e encaminhando solicitações para o aplicativo, tente enviar uma mensagem para ele e veja se ela aparece no seu terminal.

Message logged in terminalMessage logged in terminal

Adicione o seguinte à .env para mais tarde:

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

Você pode encontrar a chave privada do seu aplicativo no .nexmo_app arquivo.

Armazenar o voto no banco de dados

Logo no início de inbound.js, declare e inicialize MongoClient:

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

Abaixo da console.log(vote) instrução, conecte-se ao banco de dados e insira uma nova entrada na coleção para testar se está funcionando:

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

Aguarde até que seu servidor netlify-lambda reinicie automaticamente e envie outra mensagem para o seu LVN. Se você verificar sua coleção de votos no Atlas, um novo documento deverá aparecer.

Obtenha insights sobre números

A Number Insight API da Vonage, ao receber um número de telefone (MSISDN), fornece informações sobre ele. Existem três níveis: básico, padrão e avançado. Para esta aplicação, queremos saber o país de origem de um número, informação que é retornada como parte de uma consulta básica.

Logo acima de onde o headers são definidas, inclua o `require` e inicialize a biblioteca do cliente do Nexmo Node:

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

Observação: precisamos criar um Buffer e substituir \n para que este aplicativo funcione depois de hospedado no Netlify. Em aplicativos não hospedados no Netlify, você pode fornecer isso diretamente como process.env.VONAGE_PRIVATE_KEY.

No final do arquivo, crie uma nova função para obter o código do país a partir de um 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)
    })
  })
}

Existem outras informações que a Number Insight API irá retornar. Para esta aplicação, precisamos apenas do código ISO de 3 dígitos associado ao número de telefone. Esse código ISO também está armazenado para cada país participante em nossa countries coleção.

Acima da votes.insertOne() declaração, acrescente:

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

Envie outra mensagem para o seu LVN. O código do país deve estar registrado no terminal.

Enviar uma resposta ao usuário

Quando recebermos uma mensagem, devemos responder ao usuário e informá-lo. Na parte inferior do seu aplicativo, adicione uma função para fazer isso:

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

Agora podemos usar a função para enviar uma mensagem aos usuários e, em seguida, retornar seu valor diretamente. Substitua a return instrução no try {} bloco pela nossa nova chamada de função:

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

Envie uma mensagem para o seu LVN e você deverá receber uma resposta.

Verificar se o voto é válido

Não queremos armazenar todos os votos que nos são enviados. Existem algumas verificações necessárias para que um voto seja válido. Abaixo da votersCountry variável, crie as verificações:

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

Altere o objeto dentro de votes.insertOne() para incluir as informações que queremos armazenar:

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

Como há instruções `return` nas instruções `if`, o voto só será inserido se nenhuma das condições for atendida, o que significa que ele é válido.

Preencher com votos

Nosso sistema de votação já está pronto. No entanto, para criar endpoints de resultados, precisaremos de milhares de votos. Assim como antes, aqui está um script que adicionará 20 mil votos. Adicione este código a um novo addVotes.js arquivo no diretório 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)
  }
})

Exclua os documentos existentes e, em seguida, execute este script 5 ou 6 vezes. Seu banco de dados do MongoDB Atlas deve agora conter uma grande quantidade de votos de exemplo.

12000 records in the votes collection12000 records in the votes collection

Criar pontos de extremidade para o front-end

Existem alguns componentes em nosso front-end — precisamos de um endpoint para retornar os países que preencherão o menu suspenso e de um endpoint para retornar as pontuações de um determinado país.

Completed dashboardCompleted dashboard

Obter a lista de países

Crie um novo arquivo em /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 seu servidor netlify-lambda e, em seguida, teste executando curl http://localhost:9000/countries.

Obtenha resultados

Este endpoint aceitará um parâmetro de consulta de ?country=CODE. Copie e cole o código do endpoint “countries” em um novo arquivo chamado results.js. Substitua o conteúdo do try {} bloco pelo seguinte:

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

A topTen variável utiliza uma agregação do MongoDB para retornar as 10 primeiras entradas, conforme votadas pelo país fornecido. Em seguida, adicionamos um valor de pontos a cada uma das entradas com o valor de pontos fornecido na points array.

Reinicie o servidor e execute curl http://localhost:9000/results?country=GBR para testar.

Front-end do Scaffold

Crie um novo arquivo na raiz do projeto chamado 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>

Crie um style.css arquivo na raiz do projeto:

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

Crie um client.js arquivo na raiz do projeto:

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

Alguns pontos importantes a serem observados:

  • Nesse created() adicionamos duas propriedades a cada país — uma pontuação inicial de 0 e uma results propriedade que definimos como “true” assim que obtivermos os resultados para esse país.

  • A leftToReveal propriedade calculada inclui apenas os países que results definido como true, para que não possamos contar acidentalmente um país duas vezes.

Manter os resultados entre as atualizações

Este é um sistema bastante bom e bastante robusto. Um ponto em que podemos melhorá-lo é a persistência das pontuações entre as atualizações (caso isso ocorra durante a exibição dos resultados).

No final do getScores() método, adicione os countries dados ao localStorage:

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

Atualização created() para buscar apenas dados atualizados sobre os países caso não tenhamos nenhum no 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 }
    })
  }
},

Hospedar no Netlify

Crie um novo arquivo na raiz do seu projeto chamado.gitignore. Os arquivos e diretórios listados nesse arquivo não serão incluídos no repositório do Git. Seu arquivo deve ficar assim:

node_modules
functions/build
.env
.nexmo-app

Envie este repositório para o GitHub e, em seguida, faça login na sua conta do Netlify. Clique em “Novo site a partir do Git”, selecione o repositório e, nas Configurações básicas de compilação , o comando de compilação deve ser npm run netlify:build. Nas Configurações avançadas de compilação , adicione cada item do seu .env arquivo.

Após a implantação, há duas alterações que você precisará fazer:

  1. Atualize as URLs no seu aplicativo da API da Vonage para <netlify_url>/.netlify/functions/status (ou /inbound).

  2. Em client.js atualize seu baseURL método da seguinte forma:

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

Envie um novo commit e seu site do Netlify será reimplantado automaticamente.

Conclusão e próximos passos

Existem vários componentes envolvidos neste aplicativo. No entanto, cada um deles desempenha seu papel para criar um sistema de votação do Eurovision que realmente funcione.

É possível obter vários LVNs de diferentes países usando a CLI do Nexmo ou pelo painel de controle da web. Os usuários continuarão podendo votar apenas uma vez, independentemente do LVN para o qual enviem a mensagem. Uma melhoria que você talvez queira implementar é fechar a janela de votação para que todos os países tenham o mesmo período para votar.

Você pode encontrar o projeto final em https://github.com/nexmo-community/eurovision-voting-system-js

Como sempre, se precisar de ajuda, sinta-se à vontade para entrar em contato na Slack da Comunidade de Desenvolvedores da Vonage. Esperamos vê-lo por lá.

Islândia A Islândia, aliás, teve a melhor participação em 2020.

Compartilhar:

https://a.storyblok.com/f/270183/400x400/c822f15b89/kevinlewis.png
Kevin LewisEx-funcionários da Vonage

Ex-representante de desenvolvedores da Vonage, onde sua função era apoiar a comunidade tecnológica local em Londres. Ele é um experiente organizador de eventos, jogador de jogos de tabuleiro e pai de um cachorrinho fofo chamado Moo. Ele também é o principal organizador do You Got This — uma rede de eventos sobre as habilidades essenciais necessárias para uma vida profissional feliz e saudável.