- USG Homepage Docs
Die Seite ist unter usginfo.ch erreichbar.
Wir erstellen eine dynamische Webseite mit React und Typescript. NodeJS verwenden wir als Runtime für den Reactserver und JEST für das Unit Testing.
Node, React, TypeScript(tsx) und JEST
Unter anderem werden wir vorübergehend eine Art von Forms für alles (Bewerbungsformular, Kontaktformular, etc.) verwenden. Je nach Bedarf und Wachstum der Seite wird eventuell ein Backend für die API und Datenbank hinzugefügt.
Nachdem das grundlegende Frontend fertig gestellt wurde haben wir uns dafür entschieden diese Seite mit einem Backend zu ergänzen. Wir haben diesen Vorschlag an den Kunden gemacht und er war einverstanden.
Diese Ergänzung der soll die Seite in eine Art CRUD verwandeln. Bisher ist der Inhalt statisch. Administratoren werden Mitglieder hinzufügen oder löschen können. Dies benötigt ein Login-System und eine Nutzerverwaltung mit Rollen.
Um das alles zu realisieren, braucht das Backend eine API und eine Datenbank.
Dafür werden wir folgende Technologien verwenden:
deta(Cloudhost) und Express.js(API) in Kombination mit Node
Um sich ein ausgeprägtes Bild von der Webseite zu machen, haben wir uns dazu entschieden, mit dem Kunden zusammen ein Low-Fi Mockup zu erstellen. Dazu haben wir uns in einem Meeting online verabredet und haben dann zusammen das Grundgerüst des Designs für die Webseite erstellt. In dem Prozess kam es schon zu den ersten Entscheidungen, die wir trafen, die dann auch im Endergebnis erscheinen werden. Wir haben für die Erstellung des Low-Fi Mockups draw.io verwendet. Wir haben dann dem Kunden kontinuierlich Designvorschläge gemacht und er hat uns seine Meinung dazu gesagt. In den meisten Fällen haben ihm unsere Vorschläge gefallen. In dem Meeting ist dann folgendes Resultat erschienen:
Homepage:
Feed:
Kontakt:
Ueber uns:
Unser Team:
Ein Mockup mit draw.io erstellen.
Wir haben unser Mockup mit draw.io erstelt und empfehlen es auch sehr, dass man Low-Fi Mockups ebenfalls damit erstellt. Dafür muss man die Seite aufrufen (oben mehrmals verlinkt) und auf neues Diagramm erstellen klicken. Nun kann man selber wählen, welche Designs man verwenden will. Das ist jedem das seine.
Man kann nun beginnen, ein Mockup für viele verschiedene Sachen zu erstellen.
Zur Vorbereitung für alles muss man das Repository forken, falls man kein eingetragener Contributor ist. So erstellt man eine eigene Kopie vom Repo, in der man dann die Änderungen umsetzen kann.
Als nächstes muss die Repo geclonet werden. Danach kann man die Working-Directory zum geclonten Repo wechseln, um in dieser dann die Änderungen vorzunehmen.
git clone https://github.com/[Dein Nutzername]/USG_Website.git
cd USG_Website/
Es ist essentiell Node auf dem Arbeitsgerät installiert zu haben! Der Download ist hier zu finden.
Sobald man das ganze Projekt auf dem Gerät lokal hat, muss man alle Node-Packete installieren. Dies tut man, indem man in die Directory mit der eigentlichen React-Applikation wechselt und dann den folgenden Befehl dort ausführt.
cd usg-website/
npm install
Es sollten alle Dependencies installiert werden.
Eventuell werden Warnungen wegen Vulnerabilities angezeigt. Diese sind momentan (September 2022) zu ignorieren, da es noch keine einfachen Fixes für diese gibt.
Unter anderem ist npm audit
bezüglich Frontend-Anwendungen nicht immer akkurat. Für die Gewähleistung der Sicherheit haben wir deshalb Snyk im Repo installiert.
Um den lokalen Server zu starten, muss der untere Befehl in der Shell ausgeführt werden. Die Website sollte sich automatisch im Default-Browser des Geräts öffnen. (localhost:3000 Standardmässig)
Befehl in der Directory mit der React-Applikation ausführen!
npm start
Ansonsten kann man mit dem nächsten Befehl auch einen statischen Server verwenden. Befolgen Sie danach die Anweisungen von NPM. Diese sollten im Terminal nach dem Build erscheinen.
npm run build
Wir bitten alle Kontributoren/Kollaboratoren die folgenden Guidelines zu befolgen. Kontributionen, welche die Konventionen nicht einhalten, werden nicht akzeptiert.
Grundsätzlich ist die Sprache für dieses Projekt Hochdeutsch. Kommunikation und Dokumentation soll möglichst formell geführt werden. Unangemessene oder provokante Ausdrücke werden nicht tolleriert.
Commitnachrichten und Pull Requests sind alternativ auch auf Englisch erlaubt, solange die Verständlichkeit nicht beeinträchtigt wird.
npm audit
und Snyk warnen vor einer nth-check Vulnerability. Diese gilt es zu ignorieren bis ein offizieller fix gefunden wird.
Andere Vulnerabilities gilt es schnellst möglich zu beheben.
Interne Pull Requests sind obligatorisch, da der main Branch geschützt ist. Pull Request fürs Deployment müssen durch den Code-Owner bestätigt werden.
Alle normalen Pull Requests benötigen ein Code-Review, damit sie gemerget werden können.
Es soll sichergestellt werden, dass Pull Requests in Verbindung mit Issues korrekt verlinkt werden, damit das Issue beim Merge automatisch geschlossen wird.
Mit externen Pull Requests meinen wir Pull Requests, welche von nicht Kollaboratoren des Repos erstellt wurden.
Kontributionen externer Personen sollen via Fork in einer Pull Request vorgeschlagen werden. Hierfür sind die folgenden Schritte zu befolgen. (als Hilfe)
- Zuerst muss ein Fork des originalen Projekts erstellt werden. Um diesen Fork nicht immer neu erstellen zu müssen, kann man mit dem "Sync"-Button auf dem Fork alle Erneuerungen seit dem letzten Fork kopieren.
- Nun können Commits auf dem Fork gemacht werden.
- Wenn alle nötigen Änderungen vorgenommen wurden, kann die Fork-Version mit der originalen gemerget werden.
- Dies kann man tun, indem man eine Pull Request (in der Originalrepo) von einem Branch des Forks zu einem Branch des originals macht. Dazu kann man einen kleinen, blauen Text bei der Erstellung anklicken.
- Die Pull Request soll im originalen Repo angezeigt werden. (ggf. kann man sie noch mit einem Issue verlinken)
Damit ein Code Review vollständig ist, müssen alle Änderungen (ausgenommen automatischen, z.B. package-lock.json) vom Reviewer untersucht werden.
Unter anderem ist der Reviewer auch dafür verantwortlich eine lokale Instanz der Applikation laufen zu lassen. Diese darf keine Errormeldungen vorweisen, damit ein Approval gegeben werden darf.
Für dieses Repository ist ein Workflow unter GitHub-Actions eingestellt. Dieser ist dafür zuständig, bei jeder Pull Request die Unit Tests automatisch auszuführen.
Wenn ein Workflow nicht erfolgreich ist, kann nicht gemerget werden. Es müssen Änderungen vorgenommen werden, bis alle Tests erfolgreich sind.
Unit tests können auch lokal, noch vor dem Committen ausgeführt werden. Dies ist mit dem npm test
Befehl möglich. Dieser Soll selbsterklärend sein, ansonsten bitte nachfragen oder sonst informieren.
Anf.-Nr. | Muss/ Kann |
funk./ qual. |
Beschreibung |
---|---|---|---|
1 | M | funk. | Alle Buttons sind funktionsfähig und sind an den richtigen Ort verlinkt |
2 | M | qual. | Strukturierte Folderstruktur soll vorhanden sein. (übersichtlich) |
3 | M | qual. | Die Dokumentation wird ausführlich geführt. Jede Komponente ist dokumentiert. |
4 | M | funk. | Die Seite ist responsive (Text und Bilder passen sich an den Bildschirm an) |
5 | M | qual. | Die Seite ist strukturiert und übersichtlich aufgebaut. Der Benutzer soll nicht überfordert sein. |
6 | K | qual. | Es wird nicht immer dieselbe Schriftgrösse und -art verwendet. Somit wirkt die Seite nicht monoton auf den User. |
7 | M | qual. | Ein Dunkles, kreatives und modernes Design ist vorhanden. (Gestaltung in Zusammenarbeit mit dem Kunden) |
8 | M | funk. | Die Hauptsprache der Seite ist Deutsch. |
9 | M | funk. | Eine Homepage ist vorhanden und sie fürht zu den jeweiligen Unterseiten. |
10 | M | funk. | Die Webseite soll dunkel (farbe) gestaltet sein. |
11 | M | qual. | Auf der Landingpage wird der Benutzer mit einem Willkomenstext begrüsst. |
12 | M | qual. | Ein Text mit dem Motto des Teams ist auf der Landingpage vorhanden. |
13 | M | funk. | Es ist auf jeder Seite eine Navigationsleiste vorhanden. |
14 | M | funk. | Die Navigationsleiste hat mindestens 3 Buttons, welche zum "Über uns", "Kontakt" und "Unser Team" führen. |
15 | K | funk. | Das Logo führt immer zurück zur Landingpage. |
16 | M | funk. | Auf der Kontaktseite ist ein Kontaktformular vorhanden. |
17 | M | funk. | Auf der Kontaktseite sind die jeweiligen Formulare für Scrims und Bewerbungen verlinkt. |
18 | K | qual. | Sonstige Kontaktmethoden sind eingebaut, falls gewünscht. (kein Wunsch, Stand 01.11.2022) |
19 | M | funk. | Auf der Über-Uns-Seite soll das bereitgestellte Zitat (im Dokument mit dem Antrag des Kunden) vorzufinden sein. |
20 | M | funk. | Die Teammitglieder sollen Tabular auf der Unser-Team-Seite vorgestellt werden. Die Stelle im Team soll dabei auch unter den Namen geschrieben sein. |
21 | M | funk. | Twitter und Discordserver (Community) von USG sind auf der Seite verlinkt. |
22 | M | funk. | Ein simpler Feed in Form von einem Twitter-Embed soll vorhanden sein. |
23 | M | funk. | Es soll auf aufkommende Scrims aufmerksam gemacht werden. Dies wird über das Twitter-Embed ermöglicht. |
Testf.-Nr. | Anf-Nr. | Vorbereitung | Testumgebung | Eingabe | Erw. Ausgabe |
---|---|---|---|---|---|
1.1 | 1 | Deployte Webseite im Browser | Alle Buttons anklicken. | Man wird immer auf die ensprechende Seite weitergeleitet. | |
2.1 | 3 | Deployte Webseite im BrowserDeployte Webseit | Für jede Komponente überprüfen, ob Doku vorhanden ist. | Jede Komponente ist mit einem Codeblock und einer simplen Beschreibung dokumentiert und ist ganz am Anfang dieser verlinkt (File). | |
3.1 | 4 | Tool für Responsivetests oder Smartphone vorbereiten. | Deployte Webseite im Browser oder Responsive-Testing-Tool | Seite in Mobil-Ansicht öffnen. | Alle Komponenten passen sich so an, dass die Seite übersichtlich bleibt in der Mobilansicht. |
4.1 | 5 | Lo-Fi Mockup oder Deployte Webseite im Browser | Einem Tester den Auftrag geben ein Scrim zu beantragen. | Auftrag soll ohne Probleme ausgeführt werden können. Der Tester darf nicht zögern oder sich beschweren. | |
5.1 | 6 | Deployte Webseite im Browser | Alle Seiten miteinander vergleichen. | Nicht alle Schriftarten und -grössen sind gleich. Es gibt ab und zu Unterschiede, die Sinn machen. (z.B. Überschrift, Bemerkungen, etc.) | |
6.1 | 7, 10 | Deployte Webseite im Browser | - | Alle Seiten haben einen eher Dunklen Hintergrund und eine helle Schrift. | |
7.1 | 8 | Deployte Webseite im Browser | - | Die angewendete Sprache ist Deutsch. | |
8.1 | 9 | Deployte Webseite im Browser**(https://usginfo.ch/)** | Zur Homepage wechseln, falls diese noch nicht angezeigt wird. | Auf der Homepage sind Buttons und eine Navigationsleiste vorzufinden. | |
9.1 | 11 | Deployte Webseite im Browser**(https://usginfo.ch/)** | Zur Homepage wechseln, falls diese noch nicht angezeigt wird. | Es soll ein Text mit einer Begrüssung und einem Logo vorzufinden sein. | |
10.1 | 12 | Deployte Webseite im Browser**(https://usginfo.ch/)** | Zur Homepage wechseln, falls diese noch nicht angezeigt wird. | Auf der Homepage soll das Motto "We are United. We are Swiss. We are Gamers." vorzufinden sein. | |
11.1 | 13 | Deployte Webseite im Browser | Jede erreichbare unterseite öffnen. | Es muss überall eine Navigationsleiste haben. | |
12.1 | 14 | Deployte Webseite im Browser | - | Es sind mindestens die Buttons "Über Uns", "Unser Team" und "Kontakt" in der Navigationsleiste vorhanden. | |
13.1 | 15 | Deployte Webseite im Browser | Logo in der Navigationsleiste anklicken. | Der Nutzer wird wieder auf die Landingpage gebracht. | |
14.1 | 16 | Deployte Webseite im Browser | Formular auf Kontaktseite ausfüllen und daten Absenden. | Testdaten wurden erfolgreich abgeschickt. | |
15.1 | 17 | Deployte Webseite im Browser | Buttons auf der Kontaktseite anklicken. | Es öffnet sich je eine Seite mit entsprechendem Formular. | |
16.1 | 19 | Deployte Webseite im Browser | Über-Uns-Seite öffnen. | Auf der Über-Uns-Seite ist das bereitgestellte Zitat eingefügt. | |
17.1 | 20 | Deployte Webseite im Browser | Unser-Team-Seite öffnen und über eine beliebige Karte hovern. | Auf der Rückseite dieser Karte sind Name und Stelle im Team der entsprechenden Person vorzufinden. | |
18.1 | 21 | Deployte Webseite im Browser | Feed und Über-Uns öffnen. | Twitter ist im Feed eingebettet und ein Einladungslink zum Discordserver ist unter Über-Uns hinterlegt. | |
19.1 | 22, 23 | Deployte Webseite im Browser | Feed öffnen. | Twitter-Embed mit den aktuellen Tweets wird angezeigt. |
Testf.-Nr. | Bericht | Tester |
---|---|---|
1.1 | OK | @h0peRL, @mnaray |
2.1 | N/OK, es fehlt Team.tsx. | @h0peRL, @mnaray |
3.1 | N/OK, Responsive nicht vorhanden. | @h0peRL, @mnaray |
4.1 | OK | @h0peRL, @mnaray |
5.1 | teilweise OK, überall der geliche Font. | @h0peRL, @mnaray |
6.1 | OK | @h0peRL, @mnaray |
7.1 | OK | @h0peRL, @mnaray |
8.1 | OK | @h0peRL, @mnaray |
9.1 | OK | @h0peRL, @mnaray |
10.1 | OK | @h0peRL, @mnaray |
11.1 | OK | @h0peRL, @mnaray |
12.1 | OK | @h0peRL, @mnaray |
13.1 | OK | @h0peRL, @mnaray |
14.1 | OK | @h0peRL, @mnaray |
15.1 | OK | @h0peRL, @mnaray |
16.1 | OK | @h0peRL, @mnaray |
17.1 | OK | @h0peRL, @mnaray |
18.1 | OK (Braucht lange zum laden) | @h0peRL, @mnaray |
19.1 | OK (Braucht lange zum laden) | @h0peRL, @mnaray |
Die entwickelte Applikation erfüllt die meisten Anforderungen (Stand 02.22.2022), die gestellt worden sind.
Diese Anforderungen hätten alle innerhalb der 4 Wochen im Lernatelier (exkl. 2 Wochen Ferien) erfüllt werden können. Wir haben manche übersehen, da wir am Anfang keine Issues zu den jeweiligen Anforderungen erstellt haben.
import React from "react";
import { Link } from "react-router-dom";
import LogoImage from "../../logos/cropped_logo.png";
function Logo() {
return (
<div>
<Link to="/">
<img src={LogoImage} alt="Logo" className="h-72"></img>
</Link>
</div>
);
}
export default Logo;
Logo.tsx nimmt die PNG-Datei des Logos, und gibt dieses in Form einer React Komponente zurück. Um den IMG-Tag herum ist eine Link Komponente, die dafür sorgt, dass man zur Homepage weitergeleitet wird wenn man darauf klickt.
Wichitg:
Beim Rendern in Testfällen ist zu beachten, dass die Komponente Link nie ohne einen Router aufgerufen werden kann. Um dies zu beheben, muss man folgenden Wrapper hinzufügen:
render(<App />, {wrapper: BrowserRouter});
import React from "react";
function Slogan() {
return (
<p className="text-2xl font-bold my-8">
We are United. We are Swiss. We are Gamers
</p>
);
}
export default Slogan;
Slogan.tsx ist eine kleine Komponente, welche den Slogan/das Motto des Teams beinhaltet.
import React from "react";
interface TitleType {
children: string;
}
function Title(source: TitleType) {
return <h1 className="text-3xl pb-8">{source.children}</h1>;
}
export default Title;
Title.tsx ist eine kleine Komponente, welche benutzt wird, um einen einheitlichen Titel auf jeder Seite zu gestalten. Dafür muss man den gewünschten Content einfach in die Component schreiben.
import React from "react";
import { Link } from "react-router-dom";
interface ButtonProps {
text: string;
destination: string;
}
function Button(props: ButtonProps) {
return (
<Link to={props.destination}>
<button
className="bg-blue-600 text-white text-lg px-3 py-1 rounded
hover:bg-blue-200 hover:text-black transition-all
duration-200"
tabIndex={-1}
>
{props.text}
</button>
</Link>
);
}
export default Button;
Button.tsx ist eine Komponente, die einen Button zurückgibt. Man muss der Komponente beim erstellen einen Anzeigetext geben und einen Ort an den man weitergeleitet wird. Es funktioniert eigentlich gleich wie ein Anchor-Tag in Html.
import React from "react";
interface Source {
url: string;
}
function GoogleForms(source: Source) {
return (
<iframe
title="Google Forms embed"
src={source.url}
width="360"
height="820"
frameBorder="0"
marginHeight={0}
marginWidth={0}
className="my-12 mx-6"
>
Wird geladen…
</iframe>
);
}
export default GoogleForms;
GoogleForms.tsx ist die Komponente, die verwendet wird, um Forms (Umfragen) von Google auf der Website einzubetten. Mit dem url
Attribut kann eine URL zu der jeweiligen Forms von Google in die Komponente eingefügt werden. Dieses Formular wird dann auf der Webseite angezeigt. Jeder Nutzer kann sofort das Formular auf der Seite ausfüllen, ohne Redirects.
Dies ist sehr angenehm und praktisch, da man sich keine neuen Konten oder Profile erstellen muss, nur ume eine kurze Frage (o.ä.) zu stellen. Somit ist die Wahrscheinlichkeit, dass ein Nutzer dieses Feature verwendet um einiges höher als mit einem Redirect.
Es erleichtert unter anderem auch die Entwicklung, da wir als Entwickler kein Backend entwickeln müssen. Es bleibt bei einer einfachen Komponente, die sich sogar verändert, wenn der Administrator des Formulars etwas im jeweiligen Formular verwändert. Dies spart sehr viel Zeit und Probleme mit dem Datenschutz, da die Personendaten von Google behandelt werden.
import React from "react";
import Title from "./components/Title";
import TwitterFeed from "./components/TwitterFeed";
function Feed() {
return (
<main>
<Title>Unser Feed</Title>
<p className="p-12 text-2xl text-justify">
In unserem Feed wirst du immer auf dem laufenden gehalten, was gerade so
ansteht.
</p>
<TwitterFeed />
</main>
);
}
export default Feed;
TwitterFeed.tsx ist die Komponente, die verwendet wird, um die Timeline eines Twitterprofils als Embed auf der Webseite darzustellen. Jeder Besucher kann dann direkt sehen, was aktuelles ansteht und kann auch direkt auf Twitter gehen, um mehr zu erfahren. Der Feed wird jedes mal wenn man auf dem verlinkten Account tweetet sofort aktualisiert. Um dies möglich zu machen, benutzen wir das "react-twitter-embed" Package aus dem Node Package Manager.
import { Link } from "react-router-dom";
import logo from "../../logos/cropped_logo.png";
import Button from "./Button";
function NavLinks() {
return (
<main className="bg-black">
<ul
className=" flex flex-row align-middle space-x-5 text-white text-xl
divide-x-2 divide-gray-600 child:pl-5 child:child:py-1
child:child:px-2 child:child:rounded child:child:text-center
child:child-hover:bg-white child:child-hover:text-black
child:child:transition-all child:child:duration-200 "
>
<Link to="/">
<li>
<div>Home</div>
</li>
</Link>
<Link to="feed">
<li>
<div>Feed</div>
</li>
</Link>
<Link to="kontakt">
<li>
<div>Kontakt</div>
</li>
</Link>
<Link to="ueber-uns">
<li>
<div>Über Uns</div>
</li>
</Link>
<Link to="unser-team">
<li>
<div>Unser Team</div>
</li>
</Link>
</ul>
</main>
);
}
export default NavLinks;
NavLinks.tsx ist zuständig für die Verlinkung der Seiten der Navleiste. Diese Komponente wurde erstellt um das Responsivedesign der Webpage zu realisieren.
import { Link } from "react-router-dom";
import logo from "../../logos/cropped_logo.png";
import Button from "./Button";
function NavLinksBurger() {
return (
<main className="bg-black">
<ul
className="flex flex-col align-middle text-white text-xl justify-center child:pl-5 child:child:py-1 child:child:px-2 child:child:rounded child:child:text-center
child:child-hover:bg-white child:child-hover:text-black child:child:transition-all child:child:duration-200 child:child:child:bg-slate-500 child:child:child:rounded"
>
<Button text="Mitglied werden" destination="kontakt/bewerben" />
<Link to="/">
<li>
<div>Home</div>
</li>
</Link>
<Link to="feed">
<li>
<div>Feed</div>
</li>
</Link>
<Link to="kontakt">
<li>
<div>Kontakt</div>
</li>
</Link>
<Link to="ueber-uns">
<li>
<div>Über Uns</div>
</li>
</Link>
<Link to="unser-team">
<li>
<div>Unser Team</div>
</li>
</Link>
</ul>
</main>
);
}
export default NavLinksBurger;
NavLinksBurger.tsx ist zuständig für die Verlinkung der Seiten der Burger-Navleiste. Diese Komponente wurde erstellt um das Responsivedesign der Webpage zu realisieren. Sie unterscheidet sich hauptsächlich im CSS zu der normalen NavLinks.tsx. Diese Navlinks sind nur für die Anzeige von kleineren Screens zuständig.
import React from "react";
import { Link } from "react-router-dom";
import logo from "../../logos/cropped_logo.png";
import Button from "./Button";
import NavLinks from "./NavLinks";
function Navbar() {
return (
<header className="flex flex-row justify-between w-full p-5 items-center md:hidden bg-black">
<nav className="flex flex-row justify-start items-center">
<div className="h-20 mr-10">
<Link to="/">
<img
src={logo}
alt="Logo des Teams"
className="hover:scale-110 transition-all duration-200 h-full"
/>
</Link>
</div>
<NavLinks />
</nav>
<div className="flex flex-row">
<div className="m-2">
<Button
text="GitHub-Repository"
destination="https://github.com/mnaray/USG_Website"
/>
</div>
<div className="m-2">
<Button text="Mitglied werden" destination="kontakt/bewerben" />
</div>
</div>
</header>
);
}
export default Navbar;
Navbar.tsx ist die Navigationsleiste der Seite. Alle grösseren Unterseiten sind durch sie erreichbar. Zusätzlich ist noch ein Knopf eingebaut, der direkt zum Bewerbungsformular führt.
Hier ist das Styling mit Tailwind ein wenig speziell, da wir "child:" verwenden. Dafür haben wir ein kurzes Plugin in die Tailwinds-Konfiguration geschrieben. Dies ermöglicht Childitems des momentan behandelten Containers anzusprechen. Da wir eine Liste haben, und es keinen zusätzlichen Komponenten für die Listitems gibt, ist dies sehr hilfreich und spart viel Arbeit. So können wir das ganze Styling nur ein einziges mal bei <ul>
machen und es bezieht sich dann auf alle <li>
, welche sich drin befinden.
import React from "react";
import { Link, NavLink } from "react-router-dom";
import logo from "../../logos/cropped_logo.png";
import Button from "./Button";
import { CgMenuRound } from "react-icons/cg";
import { useState } from "react";
import NavLinksBurger from "./NavLinksBurger";
function BurgerMenu() {
const [open, setOpen] = useState(false);
return (
<header className="flex flex-col justify-center w-full p-5 hidden md:flex items-center bg-black">
<nav className="flex flex-col justify-start items-center">
<CgMenuRound
className="text-5xl text-start cursor-pointer"
onClick={() => setOpen(!open)}
color="white"
/>
{open && <NavLinksBurger />}
</nav>
</header>
);
}
export default BurgerMenu;
BurgerMenu.tsx ist die Navigationsleiste der Seite auf kleinen Bildschirmen. Alle grösseren Unterseiten sind durch sie erreichbar. Zusätzlich ist noch ein Knopf eingebaut, der direkt zum Bewerbungsformular führt.
Hier wurde eine Funktion funktion geschrieben die es ermöglicht das Menu auszufahren, wenn das Menu-Icon betätigt wird. Wenn sich die seite öffnet ist der Standartwert false. Sobald aber das Menu geöffnet wird, wird der Wert immer umgekehrt. Das heisst wenn der Wert true ist und man nochmal auf das Menu-Icon klickt, dann wird der Wert wieder auf false gestellt.
import React, { useEffect, useState } from "react";
import DefaultImage from "../../logos/USG_Logo_Transparent_PNG.png";
interface membercard {
img: string;
name: string;
funktionIG?: string;
teamrolle: string;
comment?: string;
}
interface filesResponse {
type: string;
data: Buffer;
}
function Membercard(source: membercard) {
const [memberImage, setMemberImage] = useState<string>(DefaultImage);
const bufferToArrayBuffer = (buf: Buffer) => {
const ab = new ArrayBuffer(buf.length);
const view = new Uint8Array(ab);
for (let i = 0; i < buf.length; ++i) {
view[i] = buf[i];
}
return ab;
};
const arrayBufferToBase64 = async (arrayBuffer: ArrayBuffer) => {
return new Promise<string | any>((resolve, reject) => {
var blob = new Blob([arrayBuffer]);
var reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = (err) => reject(err);
reader.readAsDataURL(blob);
});
};
const getMemberImage = async (path: string) => {
// unnötige Request bei leerem Pfad vermeiden
if (path.trim() === "") return;
const response = await fetch(
"https://api.usginfo.ch/files/download/" + path,
{
method: "GET",
}
);
// wenn nichts gefunden, dann die
// Konvertierung überspringen
if (response.status === 404) return;
try {
const responseJson: filesResponse = await response.json();
const arrayBuffer: ArrayBuffer = await bufferToArrayBuffer(
responseJson.data
);
const base64: string = await arrayBufferToBase64(arrayBuffer);
setMemberImage(base64);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
getMemberImage(source.img);
});
return (
<div className="flip-card m-10 rounded">
<div className="flip-card-inner rounded">
<div className="flip-card-front rounded">
<img
className="h-full w-full aspect-7/9 rounded overflow-hidden"
src={memberImage}
alt={"Picture of " + source.name}
/>
<p className="align-text-bottom text-2xl font-bold">{source.name}</p>
</div>
<div className="flip-card-back rounded py-5 px-3 bg-slate-700">
<h1 className="text-4xl">{source.name}</h1>
<p className="text-xl">{source.funktionIG}</p>
<p className="pt-2">Teamrolle:</p>
<p className="pb-1 text-lg font-medium">{source.teamrolle}</p>
<p className="text-lg py-2">{source.comment}</p>
</div>
</div>
</div>
);
}
export default Membercard;
Membercard.tsx stellt ein einzelnes Mitglied aus dem USG-Team dar. Um die Informationen wie Pseudonym, die Rollen und einen kleinen Kommentar zum Mitglied zu erhalten, muss man über die Membercard des Mitglieds hovern damit sie sich umdreht und die Informationen präsentiert.
Eine Membercard fordert folgende Properties:
img = "Foto [base64 oder Module]"
Foto des Mitglieds.
name = "Pseudonym"
Pseudonym des Mitglieds.
functionIG = "Rolle"
Rolle im Spiel. (z.B. Support, Fragger, Intel, etc.)
teamrolle = "Rolle"
Rolle in der Organisation.
comment = "Kommentar"
Kommentar über das Mitglied.
Beim comment
und functionIG
Property ist noch speziell, dass sie optional sind:
comment?: string; // Ein Fragezeichen macht das property nullable.
Rendern vom Bild:
- Zuerst wird die Komponente mit dem Logo als das
img
gerendert. - Währenddessen schickt der Client eine GET-Request an den Server für das Bild mit dem Entsprechenden Pfad.
- Der Server schickt ein JSON mit einem Buffer (BLOB in ein Array an Uint8 aufgeteilt) zurück.
- Der Client verwandelt das dann mit
bufferToArrayBuffer()
wieder in einen ArrayBuffer (grundsätzlich ein Uint8 Array). - Den ArrayBuffer geben wir dann durch die Funktion
arrayBufferToBase64()
und erhalten einen String an base64. - Wir setzen diesen String mit
setMemberImage
als den Source für das Bild. Das DOM wird manipuliert und somit das Bild angezeigt.
import React, { useEffect, useState } from "react";
import Membercard from "./Membercard";
interface member {
key: string;
name: string;
funktionIG: string;
teamrolle: string;
comment: string;
imgPath: string;
}
interface membersResponse {
items: member[];
count: number;
}
function MembercardGrid() {
const [peopleData, setPeopleData] = useState<member[]>([
{
key: "",
name: "Loading...",
funktionIG: "",
teamrolle: "",
comment: "",
imgPath: "",
},
]);
const getPeopleData = async () => {
const response = await fetch("https://api.usginfo.ch/members", {
method: "GET",
});
try {
const responseJson: membersResponse = await response.json();
setPeopleData(responseJson.items);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
getPeopleData();
}, []);
const cardsArray = peopleData.map((person) => {
return (
<Membercard
img={person.imgPath}
name={person.name}
funktionIG={person.funktionIG}
teamrolle={person.teamrolle}
comment={person.comment}
/>
);
});
return (
<div className="flex flex-row flex-wrap justify-evenly max-w-screen-lg px-3">
{cardsArray}
</div>
);
}
export default MembercardGrid;
MembercardGrid.tsx fetcht die momentanen Mitglieder des Teams von der API und stellt diese dann tabular mit flex-wrap dar. Das macht es mehr oder weniger responsive und anpassungsfähig an die momentane Menge an Mitgliedern.
Die Daten kommen von der API im Format von dem membersResponse
Interface, welches einen Count hat und ein Array an Objekten mit dem member
Interface. Diese Daten werden dann mit der map-Methode von React in die jeweiligen Membercard.tsx Komponenten als Props eingefügt. Zurück bekommen wir ein Array an Membercard.tsx Komponenten, die wir ganz einfach in ein <div>
setzen.
NOTIZ: Die Bilder werden in Membercard.tsx gefetcht und gerendert.
import React from "react";
function Willkommenstext() {
return (
<div className="text-3xl text-center w-1/3">
<p className="mb-2">Willkommen auf unserer Homepage!</p>
<p className="text-xl">
Bewirb dich bei uns oder vordere uns zu einem Scrim heraus. Du kannst
aber auch ein wenig mehr über uns und unser Team herausfinden.
</p>
</div>
);
}
export default Willkommenstext;
Willkommenstext.tsx beinhaltet ganz einfach den Willkommenstext auf der Startseite.
Er besteht aus einem kleinen Paragraphen, welcher eine Art "Untertitel" beinhaltet. Unter anderem befindet sich auch noch ein kleiner Text unter dem Untertitel, welcher den Nutzer auf seine Möglichkeiten aufmerksam macht.
import React from "react";
import Logo from "./components/Logo";
function PageNotFound() {
return (
<main>
<h1 className="text-red-600">Error 404: Page not found!</h1>
<p>Bitte eine valide URL eingeben.</p>
<Logo />
</main>
);
}
export default PageNotFound;
PageNotFound.tsx ist praktisch die 404-Seite der Webseite. Sie gibt einen kleinen Text zur Information aus und zeigt unten dran das Logo von USG. Mit einem Klick auf das Logo gelangt man wieder auf die Homepage.
Diese geschieht wenn der Nutzer eine ungültige URL angibt. Wenn der Router sie nicht erkennt, wird man automatisch auf die 404-Seite weitergeleitet, da diese Route in diesem Fall nicht existiert.
import React from "react";
import "../css/tailwind.css";
import Button from "./components/Button";
import Logo from "./components/Logo";
import Slogan from "./components/Slogan";
import Willkommenstext from "./components/Willkommenstext";
function Home() {
return (
<main>
<Logo />
<Slogan />
<Willkommenstext />
<div className="flex flex-row justify-evenly min-w-fit py-8">
<Button text="Bewirb Dich!" destination="../../kontakt/bewerben" />
<p className="text-2xl px-2">,</p>
<Button text="Scrim Us!" destination="../../kontakt/scrim-us" />
<p className="text-2xl px-2">oder</p>
<Button text="Über Uns" destination="ueber-uns" />
</div>
</main>
);
}
export default Home;
Home.tsx ist die Startseite der ganzen Webapp. Der Nutzer wird mit dem Logo, dem Slogan, und dem Willkommenstext begrüsst.
Diesem werden seine Optionen durch einen kleinen Text vorgestellt und dann kann er unten zwischen den drei Buttons wählen. Alternativ könnte man auch die Navigationsleiste verwenden, um an diese Stellen auf der Seite zu gelangen.
import React from "react";
import Title from "./components/Title";
import TwitterFeed from "./components/TwitterFeed";
function Feed() {
return (
<main>
<Title title="Unser Feed" />
<p className="p-12 text-2xl">
In unserem Feed wirst du immer auf dem laufenden gehalten, was gerade so
ansteht.
</p>
<TwitterFeed />
</main>
);
}
export default Feed;
Feed.tsx ist die Seite, in der der Twitter-Feed angezeigt wird. Dieser wird durch die TwitterFeed Komponente geladen, welche man innerhalb des Main-Tags sehen kann.
import React from "react";
import Button from "./components/Button";
import GoogleForms from "./components/GoogleForms";
import Title from "./components/Title";
function Kontakt() {
return (
<main>
<Title title="Kontakt" />
<p className="text-xl text-justify w-1/3 py-8">
Du kannst uns auf verschiedene Arten und Weisen kontaktieren. Um dich
bei uns zu bewerben, kannst du nach unten scrollen und unser Formular
ausfüllen. Wenn du gegen uns scrimmen willst, klickst du auf den
untenstehenden Button "Scrim Us!". Alternativ kannst du uns auch auf
Social-Media erreichen.
</p>
<div className="flex flex-row justify-evenly min-w-fit py-12">
<Button text="Bewirb Dich!" destination="bewerben" />
<p className="text-2xl px-3">oder</p>
<Button text="Scrim Us!" destination="scrim-us" />
</div>
<h1 className="text-2xl pt-5">Kontakformular allgemein</h1>
<GoogleForms url="https://docs.google.com/forms/d/e/1FAIpQLSes1z9E7D4g9afB-2oP27qLo1iv_mKeahRVAIIiJFQzR-JKmw/viewform?embedded=true" />
</main>
);
}
export default Kontakt;
Kontakt.tsx gibt dem Nutzer die Option, Kontakt mit dem Team via einem eingebetteten Google Forms aufzunehmen.
Zusätzlich gibt es noch zwei Buttons, welche zu den zwei Unterseiten von Kontakt führen. So kann der Nutzer sich ohne grossen Aufwand spontan Bewerben oder das Team zu einem Scrim hearausfordern.
import React from "react";
import Title from "./components/Title";
import GoogleForms from "./components/GoogleForms";
function Bewerben() {
return (
<main>
<Title title="Bewerben" />
<p className="text-xl text-justify w-1/3 py-8">
Wenn du Lust hast bei unserem Team mitzuwirken, dann kannst du dich
gerne mit dem Formular unten bei uns bewerben.
</p>
<h1 className="text-2xl pt-5">Bewerbungsformular</h1>
<GoogleForms url="https://docs.google.com/forms/d/e/1FAIpQLSf28NG92Ewt_RPfbBKdgmjTVSSyHpBZYSuxnhvuKw3XzxNHow/viewform?embedded=true" />
</main>
);
}
export default Bewerben;
Bewerben.tsx ermöglicht es dem Nutzer sich für das Team zu bewerben. Dies geschieht durch ein eingebettetes Google Forms.
import React from "react";
import GoogleForms from "./components/GoogleForms";
function Scrims() {
return (
<main>
<h1 className="text-3xl pb-4">Scrim Us!</h1>
<p className="text-xl text-justify w-1/3 pb-8">
Wir sind gerne für Scrims verfügbar! Stelle uns einen Antrag mit dem
unteren Formular. Wir werden dann auf dich zukommen, um genauere Details
zu besprechen.
</p>
<h1 className="text-3xl pb-4">Was ist ein Scrim?</h1>
<p className="text-xl text-justify w-1/3 pb-8">
Ein Scrim ist eine Art kleines Turnier zwischen zwei Teams. Es gibt
keinen Preis, der einzige Zweck ist das Team zu trainieren und neue
Strategien zu testen.
</p>
<h1 className="text-2xl pt-5">Scrim Beantragen</h1>
<GoogleForms url="https://docs.google.com/forms/d/e/1FAIpQLScUOb_E594BYsa4Vt5SzJ98VBiXxk5qnwZLluo0RwCQPRlv_g/viewform?embedded=true" />
</main>
);
}
export default Scrims;
Scrims.tsx ist die Seite, welche zuständig für das Buchen von Scrims ist. Die Grundidee ist es, dem Nutzer die Möglichkeit zu geben mit uns in Kontakt zu treten, um ein Scrim festzulegen.
Was genau ein Scrim ist, wird auch auf dieser Seite in einem Paragraphen beschrieben.
import React, { useState, useEffect } from "react";
import Button from "./components/Button";
function UeberUns() {
const [count, setCount] = useState<number | string>("...");
const getCount = async () => {
const response = await fetch("https://api.usginfo.ch/members", {
method: "GET",
});
try {
const responseJson = await response.json();
setCount(responseJson.count);
} catch (error) {
console.error(error);
}
};
useEffect(() => {
getCount();
}, []);
return (
<main>
<div className="flex flex-col items-center w-1/3">
<p className="justify-center text-5xl m-5">Über Uns</p>
<p className="py-5 justify-center text-justify text-lg">
Wir sind ein neues Schweizer E-Sport-Team, welches Rainbow Six Siege
spielt. Wir suchen gerade aktiv nach Mitgliedern. Also falls ihr
Interesse an einem Anfang in der E-Sportszene habt, seid ihr hier
richtig. Wir bieten Freundlichkeit, keinen Druck, eine Mitgliedschaft
bei unserem ESports-Team. Wir haben keine Ranganforderung, somit kann
jeder von jedem lernen und man kann zusammen Spass haben. Bei uns
bieten wir Teamfähigkeit und Mut, wir bieten Fairness und Skill.
Gesucht sind Leute wie du!
</p>
<p className="py-3 justify-center text-center text-lg">
Das Team besteht derzeit aus {count} Personen. Wir suchen noch
Personen aus dem schweizerdeutschen Sprachraum.
</p>
<div className="flex flex-column">
<Button text="Bewirb Dich!" destination="../../../kontakt/bewerben" />
<p className="text-2xl px-2">oder</p>
<Button text="Scrim Us!" destination="../../../kontakt/scrim-us" />
</div>
</div>
</main>
);
}
export default UeberUns;
Mit UeberUns.tsx erhält man einzelne Details zu dem USG-Team. Es wird erläutert wer sie sind und was sie machen.
Zusätzlich gibt es noch zwei Buttons, welche zu den zwei Unterseiten von Kontakt führen. So kann der Nutzer sich ohne grossen Aufwand spontan Bewerben oder das Team zu einem Scrim herausfordern.
import React from "react";
import MembercardGrid from "./components/MembercardGrid";
import Title from "./components/Title";
function Team() {
return (
<main>
<Title>Unser Team</Title>
<MembercardGrid />
</main>
);
}
export default Team;
Team.tsx ist zuständig für das Anzeigen vom MembercardGrid.
import React from "react";
import ReactDOM from "react-dom/client";
import "./css/index.css";
import Router from "./pages/Router";
import reportWebVitals from "./reportWebVitals";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<Router />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(console.log);
index.tsx nimmt das statische HTML Root-Element, um dieses dann zu modifizieren, indem es die aufgelisteten Komponenten unter root.render();
ladet.
In unserem Fall wird die Router.tsx Komponente geladen, welches dann den Rest der Arbeit übernimmt.
Auf der letzten Zeile werden Performance-Statistiken in der Webkonsole geloggt. Diese Methode wird nach der Entwicklungsphase entfernt.
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Layout from "./Layout";
import Bewerben from "./Bewerben";
import Feed from "./Feed";
import Home from "./Home";
import Kontakt from "./Kontakt";
import PageNotFound from "./PageNotFound";
import Scrims from "./Scrims";
import Team from "./Team";
import UeberUns from "./UeberUns";
function Router() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="feed" element={<Feed />} />
<Route path="kontakt">
<Route index element={<Kontakt />} />
<Route path="bewerben" element={<Bewerben />} />
<Route path="scrim-us" element={<Scrims />} />
</Route>
<Route path="ueber-uns" element={<UeberUns />} />
<Route path="unser-team" element={<Team />} />
<Route path="*" element={<PageNotFound />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default Router;
Router.tsx wird von index.tsx geladen. Es importiert die Elemente BrowserRouter, Routes und Route von react-router-dom, damit es ein Client-Sided-Routing ermöglichen kann. Ansonsten wäre eine React-Applikation nur eine einzige Seite.
Die Routerkomponente gibt einen BrowserRouter mit den vorgegebenen Routen zurück. Diese können dann in der Search-Bar abgerufen werden.
Eine Routenkomponente braucht einen path="Pfad"
und ein element={tsx-Komponente}
, welches beim Abruf des Pfades geladen wird.
path="/"
ist der Pfad für die Homepage der Website. Diese Route wird abgerufen, wenn man nichts (oder nur "/") hinter der Domain der Seite eingibt.
index
ist hierbei die Standarroute, die geladen wird, wenn man nichts nach dem /
eingibt.
path="myRouteName"
ist der Pfad für eine andere Unterseite von path="/"
. Sie wird normalerweise direkt unter der Standardroute eingefügt, wenn man eine Layout-Komponente hat.
Ein Beispiel ist hier zu finden. (gemeint ist Punkt 4. und 5.)
path="*"
sind alle Pfade, welche nicht existieren. In unserem Fall laden wir PageNotFound.tsx, um dem User eine 404-Meldung zu geben.
- Die Einleitung für dieses Projekt ist unter Ergänzungsprojekt zu finden.
- Das IPERKA ist unter IPERKA Ergänzungsprojekt zu finden.
Anf.-Nr. | Muss/Kann | funk./qual. | Beschreibung |
---|---|---|---|
1 | Muss | funk. | Ein funktionales Loginsystem muss vorhanden sein |
2 | Muss | funk. | Datenbank mit Nutzern muss vorhanden sein |
3 | Muss | funk. | Nutzer sollen eine Rollen haben |
4 | Muss | funk. | Ein GUI zur Bearbeitung der Daten soll vorhanden sein |
5 | Muss | funk. | Nur Administratoren können dieses GUI bedienen |
6 | Muss | funk. | Mit dem GUI kann man neue Teammitglieder hinzufügen |
7 | Muss | qual. | Die Daten der Teammitglieder sollen per GUI bearbeitbar sein |
8 | Muss | funk. | Man soll mit dem GUI einzelne Teammitglieder löschen können |
9 | Muss | funk. | Es muss eine Datenbank vorhanden sein, die über die API zugänglich ist. |
10 | Muss | funk. | Das Frontend soll Daten aus dieser DB fetchen und sie in die Seite einbauen (mappen) |
11 | Kann | qual. | Es sind keine statische Daten mehr im Frontend. (z.B. Anzahl Mitglieder) |
12 | Muss | funk. | Die Login-Datenbank ist nur für den Express-Server zugänglich. (mit API-Key) |
13 | Kann | qual. | Das Frontend soll neu ein Mobile-View haben (responsive) |
14 | Muss | qual. | Die Teammitglieder sollen Bilder anstatt Logos auf den Karten bekommen |
15 | Kann | qual. | Alles Lorem Ipsum Text wird ersetzt. |
16 | Muss | funk. | Alle Bilder sind auf einem Fileserver in der Cloud gespeichert |
Tetf.-Nr. | Anf.-Nr. | Vorbereitung | Testumgebung | Eingabe | Erw. Ausgabe |
---|---|---|---|---|---|
1.1 | 1, 9 | Testnutzer auf DB erstellt | Loginseite | Logindaten | Nutzer wird eingeloggt |
2.1 | 2, 9 | Testnutzer auf DB erstellt | API-Testumgebung (z.B. Postman) | Get-Request für alle Nutzer | Array mit Nutzern wird zurückgegeben von der API |
3.1 | 3, 9 | Testnutzer auf DB erstellt mit Rolle | API-Testumgebung (z.B. Postman) | Get-Request für spezifischen Nutzer | Nutzer mit detaillierten Daten wird ausgegeben (keine Personendaten) |
4.1 | 4, 9 | Admin auf DB erstellt und eingeloggt | Adminzone auf der Seite | - | GUI wird angezeigt |
5.1 | 5 | Nicht anmelden | Webseite | Adminzone aufrufen | keine Berechtigung oder 404 |
6.1 | 6, 9 | Admin auf DB erstellt und eingeloggt | Adminzone, Mitgliederverwaltung | Daten eingeben und Teammitglied hinzufügen | Mitglieder erscheint auf der Unser-Team Seite |
7.1 | 7, 9 | Teammitglieder wurden hinzugefügt | Adminzone, Mitgliederverwaltung | Mitglied bearbeiten | Änderungen erscheinen auf der Unser-Team Seite |
8.1 | 8, 9 | Teammitglieder wurden hinzugefügt | Adminzone, Mitgliederverwaltung | Mitglied entfernen/löschen | Mitglied verschwindet von der Unser-Team Seite |
9.1 | 10, 9 | Datenbank & API existieren | Webseite | Unser-Team Seite öffnen | Alle Mitglieder erscheinen |
9.2 | 9, 10, 11 | Datenbank & API existieren | Webseite | Über Uns Seite öffnen | Anzahl Mitglieder unter dem Text stimmt immer. |
10.1 | 12 | Login-DB existiert | API-Testumgebung (z.B. Postman) | ohne Schlüssel die DB direkt abfragen (ohne API) | keine Berechtigung oder 404 |
10.2 | 12 | Login-DB existiert und Express-API ist funktionsfähig | API-Testumgebung (z.B. Postman) | GET auf /login-access Pfad der API | "true" für Zugriff, "false" für keinen Zugriff |
11.1 | 13 | Mobiltelefon (oder Emulator im Browser) | Seite öffnen | Alles ist gut lesbar. Nichts ist überlappend und nichts hat falsche Seitenverhältnisse | |
12.1 | 14 | Webseite | Unser-Team öffnen | Bilder erscheinen anstatt Logos auf den Karten. | |
13.1 | 15 | Webseite | Webseite öffnen und jeden Text anschauen | Kein Lorem Ipsum vorhanden | |
14.1 | 16 | Fileserver in der Cloud ist vorhanden | API-Testumgebung (z.B. Postman) | GET für Bild von einem Teammitglied | Entsprechendes Bild wird zurückgegeben. |
Testf.-Nr. | Bericht | Tester |
---|---|---|
1.1 | NOK, Login noch nicht korrekt implementiert. | @h0peRL, @mnaray |
2.1 | OK | @h0peRL, @mnaray |
3.1 | OK | @h0peRL, @mnaray |
4.1 | OK | @h0peRL, @mnaray |
5.1 | NOK, Login noch nicht korrekt implementiert. | @h0peRL, @mnaray |
6.1 | OK | @h0peRL, @mnaray |
7.1 | OK | @h0peRL, @mnaray |
8.1 | OK | @h0peRL, @mnaray |
9.1 | OK | @h0peRL, @mnaray |
9.2 | OK | @h0peRL, @mnaray |
10.1 | @h0peRL, @mnaray | |
10.2 | OK (Nicht ganz genau wie im Testfall) | @h0peRL, @mnaray |
11.1 | OK | @h0peRL, @mnaray |
12.1 | OK | @h0peRL, @mnaray |
13.1 | OK | @h0peRL, @mnaray |
14.1 | OK (Bufferinhalt) | @h0peRL, @mnaray |
Es wurden praktisch alle Anforderungen bis zum 16.12.22 umgesetzt, ausser das Login. Das Login hat Probleme mit Cookies, da diese nicht über CORS übertragen werden können. Dieser Bug muss noch vor dem nächsten Deploy gefixt werden.
Deta ist ein kostenfreier Cloudanbieter, der es Entwicklern ermöglicht, kleine Microservices und NoSQL-Datenbanken für kleinere Projekte einzusetzen. Ein Datenspeicher von bis zu 10GB wird pro Account auch zur Verfügung gestellt (z.B. für Bilder, Videos oder Audios).
Deta hat ein sehr praktisches CLI, mit dem man diverse Aufgaben im Handumdrehen ausführen kann. Bei der Entwicklung sind auch andere Features in Form von einem GUI aber auch sehr Hilfreich.
Zum Beispiel Visor. Das ist eine Art von kleiner Testumgebung für APIs (ähnlich wie Postman). Man kann mit Visor verschiedene Arten von Requests verschicken und die Responses beobachten. Das ist dann sehr praktisch für das Debugging.
Das CLI kann unter anderem auch in den GitHub-Actions in einem Workflow verwendet werden. Das heisst, nicht jeder muss die Logindaten für den Account haben. Stattdessen kann man einen Access-Token von Deta in de Repo-Secrets bei GitHub verstecken und diesen Token dann in einem Workflow verwenden, um den Code zu deployen.
Doku ist noch zu führen.
GET
gibt auf dieser Route einen String mit der Hauptdomain für die Webseite usginfo.ch zurück.
GET
gibt auf dieser Route die ganze Datenbank an Teammitgliedern zurück.
POST
nimmt einen Request-Body in folgendem Format an und fügt es in die entsprechende Datenbank ein.
{
name: string,
funktionIG: string,
teamrolle: string,
comment: string
}
PUT
updatet mit der update Methode von Deta einen Eintrag in der Datenbank. Dazu nimmt es folgendes JSON-Format an. Wenn alles richtig lauft, wird null
als Response zurückgeschickt.
{
key: string,
name: string,
funktionIG: string,
teamrolle: string,
comment: string
}
DELETE
nimmt einen einfachen Key als string im JSON-Format entgegen und löscht den entsprechenden Eintrag. Es wird immer null
zurückgegeben.
GET
sucht in der Datenbank nach dem Objekt mit dem Key, der über req.params.key
in der URL durchgegeben wird. Falls es dieses Objekt gibt, wird es per Response an den Client geschickt, sonst wird ein Error mit dem Statuscode 404 zurückgegeben.
POST
nimmt das File im Anhang der Request und ladet es auf den Fileserver in der Cloud.
NOTIZ: Es gibt ein Limit von 10 GB Speicherplatz auf dem Fileserver. Möglichst komprimierte/reduzierte Bilder verwenden!
GET
gibt das File mit dem angegebenen Namen im Pfad zurück. Das JSON enthält type: "Buffer"
und data: [0..9999]
mit dem Inhalt des Buffers. data
ist in Form von einem Uint8Array. Dieses Array muess zuerst im Frontend konvertiert werden, bevor es als Bild angezeigt werden kann.
DELETE
löscht das File mit dem angegebenen name
im body. Falls kein solches File gefunden wird, gibt der Server einen 404 Statuscode zurück.
POST
nimmt im Body username
und password
an und fügt dann nach folgendem Prinzip einen Nutzer der Datenbank hinzu:
Zuerst wird nachgeschaut, ob dieser Nutzername schon existiert. Wenn dies der Fall ist, wird der Statuscode 409 zurückgegeben. Sobald Man einen validen Nutzernamen eingibt, wird dieser als unique Key für den Datensatz des Nutzers verwendet. Danach wird das Passwort mit argon2 gehasht und als Hash gespeichert. Die Datenbank beinhaltet keine Passwörter in Klartext. Es wird zusätzlich ein "isApproved" Attribut zu "false" gesetzt, damit nicht jeder einfach ein Adminkonto erstellen kann. Dieser Wert muss zuerst von Hand in der Datenbank zu "true" geändert werden, bevor sich der neue Nutzer einloggen kann. Das kann momentan nur jemand machen, der direkten Zugriff auf die Datenbank hat.
POST
nimmt auf dieser Route im Body username
und password
an und sucht zuerst in der Datenbank nach dem Nutzer mit dem angegebenen Nutzernamen.
Wenn der Nutzer nicht gefunden wird, gibt der Server einen Statuscode von 401 zurück. Wenn der Nutzer existiert, wird als nächstes geschaut, ob er überhaupt schon approved wurde, indem das "isApproved" Attribut kontrolliert wird. Falls dies nicht der Fall ist wird ein Statuscode von 403 zurückgegeben.
Wenn der Nutzer existiert und approved ist, kontrolliert der Server mit argon2, ob der Hashwert des Passworts mit dem in der Datenbank gespeicherten Hash überinstimmt. Wenn das wahr ist, erstellt der Server eine Session und schickt einen JSON Web Token an den Nutzer. Momenatan ist es noch nicht möglich mit HTTP-Only Cookies, da wir zwei verschiedene Hosts haben für das Frontend und das Backend (Cross-Origin).
Damit bei jeder Request, welche Daten verändern würde, geprüft werden kann, ob sich der Nutzer schon eingeloggt hat, speichern wir bei einem erfolgreichen Login den Nutzernamen im Token. Bei solchen Requests überprüft die Middleware checkAuth, ob der Nutzer schon einen gültigen Token hat. Wenn das nicht der Fall ist, wird ein Statuscode von 401 zurückgeschickt und die Request abgebrochen. Wenn es aber der Fall ist, fahrt der Server ganz normal mit der Requestbearbeitung fort.
Alle Rechte zu dieser Software sind Herrn Nikola Knezevic vorbehalten.
Jeder Besitzer einer Kopie dieser Softwarelösung ist gemäss der MIT-Lizenz verpflichtet sicherzustellen, dass die Lizenz mit dem passenden Copyright in seiner Kopie zu finden ist.
DISCLAIMER: Die MIT-Lizenz ist eine Copyright Lizenz, welche den Quellcode offenbart. Hier ist keine Rede von Copyleft.