
Teilen Sie:
Ehemaliger Developer Advocate bei Vonage, wo seine Aufgabe darin bestand, die lokale Tech-Community in London zu unterstützen. Er ist ein erfahrener Veranstaltungsorganisator, Brettspieler und Vater eines süßen kleinen Hundes namens Moo. Er ist auch der Hauptorganisator von You Got This - einem Netzwerk von Veranstaltungen zu den Kernkompetenzen, die für ein glückliches, gesundes Arbeitsleben erforderlich sind.
Aufbau eines serverlosen Eurovisions-Abstimmungssystems mit Node.js und Vonage
Lesedauer: 10 Minuten
Die Eurovision ist eine meiner Lieblingsveranstaltungen des Jahres. Für diejenigen, die es nicht wissen: Die Eurovision ist ein Gesangswettbewerb, der komisch, wunderbar und verrückte in gleichem Maße. Jedes teilnehmende Land entsendet einen Künstler, der einen Originalsong vorträgt - oft sind es lächerlich und brillant. Na dann los - haben a ein paar mehr Links.
Die Länder, die es ins Finale geschafft haben, treten live auf, bevor die Menschen in den teilnehmenden Ländern für ihren Lieblingsact abstimmen (ihren eigenen nicht mitgerechnet). Die Stimmen aus jedem Land werden gezählt, und als Ergebnis werden 58 Punkte vergeben: 12 für den Spitzenreiter, dann 10 und schließlich 8 bis 1. In den letzten Jahren haben Fachjurys die Hälfte der Stimmen für jedes Land vergeben, aber für dieses Projekt werden wir sie vergessen.
2019 Eurovision Leaderboard
Ich bin ein großer Eurovisions-Fan und dachte, es wäre ein lustiges Projekt, ein voll funktionsfähiges Abstimmungssystem zu bauen, das die Vonage Number Insights API nutzt, um die Herkunft einer Nummer zu validieren.
Wir werden zunächst eine Datenbank mit allen teilnehmenden Ländern einrichten. Dieser Datensatz wird auch die Finalisten hervorheben (unter Verwendung der Teilnehmer von 2019). Dann werden wir eingehende Stimmen per SMS verarbeiten, die Stimmen speichern, wenn sie gültig sind, und mit der Vonage Messages API. Schließlich werden wir ein Frontend entwickeln, das es uns ermöglicht, die Ergebnisse pro Land mit einer aktualisierten Rangliste abzurufen. Das gesamte Projekt wird auf Netlify gehostet, wobei Vue.js für unser minimales Front-End verwendet wird.
Wenn Sie nur den fertigen Code sehen wollen, finden Sie ihn unter https://github.com/nexmo-community/eurovision-voting-system-js.
Fertig? Los geht's!
Voraussetzungen
Wir brauchen ein paar Konten, damit das funktioniert. Wenn Sie das noch nicht getan haben, besorgen Sie sich ein:
Öffnen Sie das Terminal, erstellen Sie ein neues, leeres Verzeichnis für dieses Projekt und initialisieren Sie ein neues Projekt durch Eingabe von npm init -y. Sobald dies abgeschlossen ist, installieren Sie die erforderlichen Abhängigkeiten, indem Sie npm install dotenv encoding mongodb netlify-lambda nexmo@beta.
Sie benötigen außerdem die Nexmo CLI. Führen Sie npm install -g nexmo-cli@beta um es zu installieren, gehen Sie online zu Ihrem Account, um Ihren API-Schlüssel/Geheimnis zu erhalten, und führen Sie dann nexmo setup <api_key> <api_secret>.
Vonage API-Konto
Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch keines haben, können Sie sich noch heute anmelden und mit einem kostenlosen Guthaben beginnen. Sobald Sie ein Konto haben, finden Sie Ihren API-Schlüssel und Ihr API-Geheimnis oben auf dem Vonage-API-Dashboard.
Einrichten einer MongoDB-Datenbank
Wir werden eine gehostete MongoDB-Instanz auf MongoDB Atlas verwenden. Melden Sie sich bei Ihrem MongoDB Atlas Account an und erstellen Sie ein neues Projekt mit einem beliebigen Namen. Erstellen Sie einen neuen Cluster (Free Tier ist in Ordnung) - ich nenne meinen Eurovision-und warten Sie darauf, dass die Änderungen bereitgestellt werden.
Klicken Sie auf die Verbinden in Ihrem neuen Cluster, fügen Sie Ihre aktuelle IP-Adresse hinzu und erstellen Sie einen neuen MongoDB-Benutzer, der auf diese Datenbank zugreifen kann (notieren Sie sich das Passwort).
Im nächsten Fenster sehen Sie eine Reihe von Möglichkeiten, eine Verbindung zu Ihrer Datenbank herzustellen. Wählen Sie . Verbinden Sie Ihre Anwendung und kopieren Sie die URI in Ihre Zwischenablage.
.env-Datei erstellen
Bevor wir fortfahren, müssen wir eine neue .env Datei im Projektverzeichnis erstellen, die alle sensiblen Zeichenfolgen enthält, die andere nicht sehen sollen. Der Inhalt der Datei sollte lauten:
DB_URL=<Mongo DB URI>Ersetzen Sie <password> durch Ihr MongoDB-Benutzerpasswort und <dbname> durch eurovision.
Sammlungen erstellen
Klicken Sie auf die Sammlungen Schaltfläche in Ihrem Cluster, dann Eigene Daten hinzufügen um eine neue Sammlung zu erstellen. Wir sollten zwei erstellen:
Name der Datenbank:
eurovision, Name der Sammlung:countriesName der Datenbank:
eurovision, Name der Sammlung:votes
Zugriff von überall aus zulassen
Wir haben unsere eigene IP-Adresse in die Liste aufgenommen, was den Zugriff auf diese Datenbank von unserer lokalen Anwendung aus ermöglicht. Wenn wir das Projekt später einsetzen, werden wir jedoch keinen Zugriff auf statische IP-Adressen haben. Klicken Sie auf . Netzwerkzugang in der Seitenleiste, dann IP-Adresse hinzufügenund schließlich Zugriff von überall zulassen. Bestätigen Sie Ihre Änderungen, um die Einschränkungen aufzuheben.
Mit Ländern bevölkern
Im Jahr 2019 gab es 42 Eurovisionsbeiträge, von denen es 26 ins Finale schafften. Da wir diese Daten nur einmal eingeben müssen, habe ich ein Skript geschrieben, um diese Dateneingabe zu automatisieren. Erstellen Sie einen Ordner namens boilerplate, und darin eine Datei namens addCountries.js. Fügen Sie den folgenden Code in diese Datei ein:
// 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)
}
})
Speichern Sie die Datei, öffnen Sie Ihr Terminal, und führen Sie node boilerplate/addCountries.js. Überprüfen Sie nach der Fertigstellung Ihre Sammlung in MongoDB Atlas. Sie sollten 42 Dokumente in der Ländersammlung sehen.
Country entries populated in Atlas
Einrichten einer Netlify-Funktion
Für die Vonage-API-Integration müssen wir zwei Endpunkte erstellen. Der erste ist ein Status-Endpunkt, der für diese Anwendung keine Logik benötigt, sondern einen HTTP 200-Status zurückgeben muss. Um diese Endpunkte zu erstellen und zu hosten, werden wir Netlify Functions verwenden. Zuvor sind jedoch noch einige Einstellungen erforderlich.
In Ihrer package.json Datei, ersetzen Sie den scripts Abschnitt durch den folgenden:
"scripts": {
"netlify:serve": "netlify-lambda serve functions/src",
"netlify:build": "netlify-lambda build functions/src"
},Erstellen Sie eine netlify.toml Datei im Stammverzeichnis Ihres Projekts und schreiben Sie den folgenden Code:
[build]
functions = "./functions/build"Erstellen Sie schließlich ein functions Verzeichnis in Ihrem Projekt, und erstellen Sie darin ein src Verzeichnis. Alle unsere Netlify-Funktionen werden in diesem Verzeichnis erstellt.
In dem neuen functions/src Verzeichnis erstellen Sie eine status.js Datei. Darin erstellen Sie die Funktion:
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 }
}
}
Führen Sie im Terminal Folgendes aus npm run netlify:serve. Probieren Sie den neuen Endpunkt in einem anderen Terminal aus, indem Sie curl http://localhost:9000/status. Das Terminal sollte eine Antwort von ok.
Eingehende Nachrichten annehmen
Außerdem benötigen wir einen Endpunkt, der Daten empfängt, wenn unserer langen virtuellen Nummer (LVN) eine Nachricht gesendet wird. Kopieren und fügen Sie den Inhalt von status.js in eine neue Datei namens inbound.js.
Erstellen Sie den eingehenden Endpunkt
Fordern Sie am Anfang der Datei das Paket querystring an (in Node.js integriert):
const qs = require('querystring');Fügen Sie am Anfang des try Blocks fügen Sie den folgenden Code ein:
const { msisdn, to: lvn, text } = qs.parse(event.body)
const vote = text.toUpperCase().trim()
console.log(vote)Starten Sie den netlify-lambda-Server neu, öffnen Sie ein neues Terminal, und führen Sie npx ngrok http 9000 um eine öffentlich zugängliche Version Ihres netlify-lambda-Servers zu Testzwecken zu erstellen. Notieren Sie sich die temporäre ngrok-URL.
Eine Vonage API-Anwendung einrichten
Führen Sie in Ihrem Projektverzeichnis Folgendes aus nexmo app:create:
Anwendungsname: beliebig
Fähigkeiten auswählen: Nachrichten
Nachrichten Eingehende URL:
<ngrok_url>/inboundNachrichten Status URL:
<ngrok_url>/statusÖffentlich/Privat: leer lassen
Dieser Vorgang erstellt eine .nexmo-app Datei in Ihrem Verzeichnis. Wir werden sie später verwenden, aber geben Sie sie nicht weiter, da sie Ihren privaten Schlüssel enthält. Notieren Sie sich die neue Anwendungs-ID, die in Ihrem Terminal angezeigt wird (Sie finden sie auch in der .nexmo-app Datei finden).
Als nächstes müssen wir einen LVN kaufen und mit dieser Anwendung verknüpfen. Ausführen:
nexmo number:search GB --smsKopieren Sie eine Nummer und führen Sie sie dann aus:
nexmo number:buy <number>
nexmo link:app <number> <application_id>
nexmo numbers:update <number> --mo_http_url=<ngrok_url>/inboundJetzt ist der LVN eingerichtet und leitet Anfragen an die Anwendung weiter. Versuchen Sie, eine Nachricht an die Anwendung zu senden und sehen Sie, wie sie in Ihrem Terminal erscheint.
Message logged in terminal
Fügen Sie folgendes zu der .env für später:
VONAGE_KEY=<your_api_key>
VONAGE_SECRET=<your_api_secret>
VONAGE_APP=<your_application_id>
VONAGE_PRIVATE_KEY=<your_private_key>Sie finden den privaten Schlüssel Ihrer Anwendung in der Datei .nexmo_app Datei.
Abstimmung in der Datenbank speichern
Ganz oben in inbound.jserfordern und initialisieren MongoClient:
require('dotenv').config()
const { MongoClient } = require('mongodb')
const mongo = new MongoClient(process.env.DB_URL, { useUnifiedTopology: true })Unterhalb der console.log(vote) Anweisung stellen Sie eine Verbindung zur Datenbank her und schieben einen neuen Eintrag in die Sammlung, um zu testen, ob es funktioniert:
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 })Warten Sie auf den automatischen Neustart Ihres netlify-lambda-Servers und senden Sie eine weitere Nachricht an Ihren LVN. Wenn Sie Ihre Stimmensammlung in Atlas überprüfen, sollte ein neues Dokument erscheinen.
Einblicke in Zahlen erhalten
Die Vonage Number Insights API liefert bei Vorliegen einer Telefonnummer (MSISDN) Einblicke in diese Nummer. Es gibt drei Stufen - Basis, Standard und Erweitert. Für diese Anwendung möchten wir das Herkunftsland einer Nummer erfahren, das als Teil einer einfachen Suche zurückgegeben wird.
Genau über der Stelle, wo die headers definiert sind, erfordern und initialisieren Sie die Nexmo-Knoten-Client-Bibliothek:
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')
})Hinweis: Wir müssen einen Puffer erstellen und ersetzen \n damit diese Anwendung funktioniert, wenn sie auf Netlify gehostet wird. In nicht von Netlify gehosteten Applications können Sie dies direkt als process.env.VONAGE_PRIVATE_KEY.
Erstellen Sie ganz unten in der Datei eine neue Funktion, um die Landesvorwahl aus einer Nummer zu ermitteln:
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)
})
})
}
Es gibt noch weitere Informationen, die die Number Insights API zurückgibt. Für diese Anwendung benötigen wir nur den 3-stelligen ISO-Code, der mit der Telefonnummer verknüpft ist. Dieser ISO-Code wird auch für jedes teilnehmende Land in unserer countries Sammlung gespeichert.
Oberhalb der votes.insertOne() Anweisung hinzufügen:
const votersCountry = await getCountryCodeFromNumber(msisdn)
console.log(votersCountry)Senden Sie eine weitere Nachricht an Ihre LVN. Der Ländercode sollte im Terminal gespeichert sein.
Senden einer Antwort an den Benutzer
Wenn wir eine Nachricht erhalten, sollten wir dem Benutzer antworten und ihm Bescheid geben. Fügen Sie ganz unten in Ihrer Anwendung eine Funktion hinzu, die dies tut:
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' })
})
})
}
Wir können nun die Funktion verwenden, um eine Nachricht an die Benutzer zu senden, und dann ihren Wert direkt zurückgeben. Ersetzen Sie die return Anweisung in dem try {} Block durch unseren neuen Funktionsaufruf:
return await sendMessage(lvn, msisdn, 'Thank you for voting!')Senden Sie eine Nachricht an Ihre LVN und Sie sollten eine Antwort erhalten.
Prüfen, ob die Stimme gültig ist
Wir wollen nicht jede Stimme, die uns zugeschickt wird, speichern. Es gibt einige Prüfungen, die erforderlich sind, damit sie gültig sind. Unterhalb der votersCountry Variable erstellen Sie die Prüfungen:
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')
}Ändern Sie das Objekt innerhalb von votes.insertOne() so, dass es die Informationen enthält, die wir speichern wollen:
votes.insertOne({ msisdn, vote, votersCountry })Da die if-Anweisungen Return-Anweisungen enthalten, wird die Abstimmung nur eingefügt, wenn keine der Bedingungen erfüllt ist, d. h. sie ist gültig.
Mit Stimmen bevölkern
Unser Abstimmungssystem ist nun vollständig. Um jedoch Ergebnis-Endpunkte zu erstellen, benötigen wir Tausende von Stimmen. Wie zuvor, hier ist ein Skript, das 20k Stimmen hinzufügen wird. Fügen Sie diesen Code in eine neue addVotes.js Datei im Verzeichnis boilerplate ein:
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)
}
})
Löschen Sie Ihre vorhandenen Dokumente und führen Sie dieses Skript 5 oder 6 Mal aus. Ihre MongoDB-Atlas-Datenbank sollte nun eine Vielzahl von Beispielstimmen enthalten.
12000 records in the votes collection
Endpunkte für das Front-End erstellen
Es gibt einige bewegliche Teile in unserem Frontend - wir brauchen einen Endpunkt, um die Länder zurückzugeben, um die Dropdown-Liste aufzufüllen, und einen Endpunkt, um die Punktzahlen eines bestimmten Landes zurückzugeben.
Completed dashboard
Holen Sie sich die Länderliste
Erstellen Sie eine neue Datei in /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 }
}
}
Starten Sie Ihren netlify-lambda-Server neu und versuchen Sie es dann, indem Sie curl http://localhost:9000/countries.
Ergebnisse erhalten
Dieser Endpunkt akzeptiert einen Abfrageparameter von ?country=CODE. Kopieren Sie den Code des Länderendpunkts und fügen Sie ihn in eine neue Datei mit dem Namen results.js. Ersetzen Sie den Inhalt des try {} Blocks durch den folgenden:
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) }
Die Variable topTen Variable verwendet eine MongoDB-Aggregation, um die 10 besten Einträge zu ermitteln, die von dem angegebenen Land gewählt wurden. Wir fügen dann jedem der Einträge einen Punktwert hinzu, der den angegebenen Punktwert im points Array.
Starten Sie den Server neu, und führen Sie curl http://localhost:9000/results?country=GBR um zu testen.
Gerüst Front-End
Erstellen Sie eine neue Datei im Stammverzeichnis des Projekts mit dem Namen 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>
Erstellen Sie eine style.css Datei im Stammverzeichnis des Projekts:
@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;
}Erstellen Sie eine client.js Datei im Stammverzeichnis des Projekts:
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
}
}
})
Einige wichtige Punkte sind zu beachten:
In
created()fügen wir jedem Land zwei Eigenschaften hinzu - einen Anfangswert von 0 und eineresultsEigenschaft, die wir auf true setzen, sobald wir Ergebnisse für dieses Land haben.Die
leftToRevealberechnete Eigenschaft umfasst nur Länder, dieresultsgesetzt habentruegesetzt haben, so dass wir nicht versehentlich ein Land doppelt zählen können.
Ergebnisse zwischen Auffrischungen beibehalten
Das ist ein ziemlich gutes, ziemlich robustes System. Ein Punkt, an dem wir es verbessern können, ist die Beibehaltung der Punktzahlen zwischen den Aktualisierungen (sollte dies bei der Präsentation der Ergebnisse geschehen).
Am Ende der getScores() Methode fügen Sie die countries Daten zu localStorage hinzu:
localStorage.setItem('countries', JSON.stringify(this.countries))Aktualisieren Sie created() um nur dann neue Länderdaten abzurufen, wenn wir keine in localStorage haben:
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 }
})
}
},
Gastgeber auf Netlify
Erstellen Sie eine neue Datei in Ihrem Projektstamm mit dem Namen.gitignore. Die in dieser Datei aufgeführten Dateien und Verzeichnisse werden nicht in ein Git-Repository aufgenommen. Ihre Datei sollte wie folgt aussehen:
node_modules
functions/build
.env
.nexmo-appPushen Sie dieses Repository auf GitHub und melden Sie sich dann bei Ihrem Netlify Account an. Klicken Sie auf . Neue Website von Git, wählen Sie das Repository und in den Grundlegende Build-Einstellungen sollte der Build-Befehl lauten npm run netlify:build. In den Erweiterte Build-Einstellungen fügen Sie jedes Element in Ihrer .env Datei hinzu.
Nach der Bereitstellung müssen Sie zwei Änderungen vornehmen:
Aktualisieren Sie Ihre URLs in Ihrer Vonage API-Anwendung auf
<netlify_url>/.netlify/functions/status(oder/inbound).Unter
client.jsaktualisieren Sie IhrebaseURLMethode auf die folgende:
baseURL() {
if(location.hostname == 'localhost' || location.hostname == "127.0.0.1") {
return "http://localhost:9000"
} else {
return "<netlify_url>/.netlify/functions"
}
},Wenn Sie eine neue Übertragung durchführen, wird Ihre Netlify-Website automatisch neu bereitgestellt.
Nachbereitung und nächste Schritte
Es gibt eine ganze Reihe beweglicher Teile in dieser Anwendung. Aber jeder Teil erfüllt seine Aufgabe, um ein Eurovisions-Wahlsystem zu schaffen, das tatsächlich funktioniert.
Sie können mehrere LVNs aus verschiedenen Ländern über die Nexmo CLI oder über das Web-Dashboard abrufen. Nutzer können nur einmal abstimmen, unabhängig davon, welche LVN sie senden. Eine Verbesserung, die Sie vielleicht vornehmen möchten, ist die Schließung des Abstimmungsfensters, damit alle Länder den gleichen Zeitraum zur Abstimmung haben.
Das Abschlussprojekt finden Sie unter https://github.com/nexmo-community/eurovision-voting-system-js
Wie immer, wenn Sie Unterstützung benötigen, können Sie sich gerne an die Vonage Entwickler-Community Slack. Wir hoffen, Sie dort zu sehen.
Island hatte übrigens den besten Beitrag für 2020.
Teilen Sie:
Ehemaliger Developer Advocate bei Vonage, wo seine Aufgabe darin bestand, die lokale Tech-Community in London zu unterstützen. Er ist ein erfahrener Veranstaltungsorganisator, Brettspieler und Vater eines süßen kleinen Hundes namens Moo. Er ist auch der Hauptorganisator von You Got This - einem Netzwerk von Veranstaltungen zu den Kernkompetenzen, die für ein glückliches, gesundes Arbeitsleben erforderlich sind.
