Auf dieser Seite geht es um den `src/db Ordner mitsamt dessen Kinder. Falls du wissen möchtest, wie du die API startest und konfigurierst schau dir die Installationsanleitung und Docker Seite an.
Datenbank
questions.json
Diese Datei wurde genutzt um aus den Fragen SQL Abfragen zu generieren und existiert jetzt nur noch um eine menschenfreundlichere Alternative als die SQL Datei zum Überblick über Fragen zu bieten.
schema.sql
Das Datenbankschema hat alle Definitionen, wie Tabellen erstellt werden sollen, fügt aber auch schon alle definierten Fragen ein. Falls weitere Tabellen erstellt werden sollen, können die SQL Befehle dafür in diese Datei gepackt werden. Falls neue Fragen abgedeckt werden sollen, können sie zu dem INSERT INTO "question"
Statement hinzugefügt werden. Dafür sollte erst eine UUID generiert werden:
psql -U postgres
SELECT gen_random_uuid();
API
Hier is die ganze Logik für die API, welche auf NodeJS und dem express package läuft. Um das erstellen der Endpunkte zu vereinfachen und um den OpenAPI Standard zu befolgen, wurde außerdem das package express-openapi genutzt, welches die API basierend auf der Dateistruktur erstellt.
api-doc.js
Hier wird eine OpenAPI kompatible spezification erstellt, welche Informationen über die API bereitstellt. Mit dem definitions
Schlüssel können Models, welche als in- und output fungieren, definiert werden. Wie man diese Definitionen erstellt kann in der OpenAPI Dokumentation gefunden werden.
Falls typen etwas dynamischer sein sollen, können sie in der open-api-types.js
Datei hinzugefügt werden und wo nötig mit weiteren Attributen folgendermaßen erweitert werden:
Model: {
type: "object",
properties: {
id: {
type: "string"
},
...typ_zum_erweitern
}
}
Auf diese Weise können doppelte Definitionen verhindert werden
Defining endpoints
express-openapi
generiert die Endpunkte basierend auf die Ordnerstruktur in paths
. Dabei wird ein Endpoint aus seinem Pfad zusammengesetzt. Eine solche Struktur
└── give
└── me
└── examples.js
generiert den Endpunkt give/me/examples
.
Um parametrisierte Endpunkte, wie give/me/example/id1
zu generieren, muss der Parameter einfach in geschweifte Klammern gepackt werden. Um also beim Endpunkt eine dynamische id angeben zu können, muss die Dateistruktur so aussehen:
└── give
└── me
└── examples
└── {id}.js
Die JavaScript Dateien beinhalten hierbei die Logik des Endpunktes. Hier kann definiert werden, welche HTTP Anfragen akzeptiert werden, wie der Input aussieht und was zurückgegeben wird. Ein beispiel Endpunkt, der GET Anfragen für den obrigen Pfad akzeptiert würde so aussehen:
export default function() {
//Akzeptierte Anfragen
let operations = {
GET
};
// req ist die Anfrage, res die Antwort und _next erlaubt Middleware
function GET(req, res, _next) {
//Gibt den 200 Status Code und die ID zurück
res.status(200).send(`You send {req.params.id}`);
}
// Dokumentation
GET.apiDoc = {
summary: "Gibt deine ID zurück",
// Input
parameters: [
{
in: "path",
name: "id",
type: "number",
description: "Die ID"
}
],
// Hier werden alle möglichen Status Codes definiert
responses: {
200: {
description: "Sendet dir deine ID",
//Rückgabetyp
schema: {
type: "string"
}
}
}
}
}
Um einen Endpunkt zu erstellen, muss also diese nur entlang seines Pfades die Ordner aufgebaut werden und das package übernimmt den Rest.
Services
Die meisten JavaScript Dateien an den Endpunkten verweisen auf einen Service im services
Ordner. Damit wird die gesamte Logik des Endpunktes auf den Service verlagert. Meistes werden Services nach ihrer Aufgabe gruppiert, z.B. questionsService.js
kümmert sich um alle Aufgaben, die mit Fragen zu tun haben. Um neue Funktionen hinzuzufügen kann ein bestehender Service einfach erweiter werden oder eine neue Datei erstellt werden, die aber dann in index.js
importiert und zu den dependencies in initialize
hinzugefügt werden muss.
postgres.js
Diese Datei kommuniziert ausschließlich mit der Datenbank. Fast alle Services rufen Funktionen aus dieser Datei auf umd Ergebnisse aus der Datenbank zu bekommen. Diese Funktionen abstrahieren eigentlich nur SQL Abfragen an die Datenbank. Um eigene Abfragen hinzuzufügen, kann eine neue Funktion erstellt werden, die mit client.query("abfrage")
die Datenbank anfragt. Sollte diese Abfrage dynamische Parameter haben, können diese nicht einfach in den String eingearbeitet, sondern lieber so erstellt werden:
client.query("SELECT from table WHERE id = $1 AND name = $2", [id, name])
So werden die Eingaben bereinigt um SQL Injections zu verhinder. Die Zahl vorm Dollarzeichen ($) ist der Index des Objektes in der Liste, was an der Stelle eingefügt wird.