diff --git a/web/08/labor/08_loesungen/02/go.mod b/web/08/labor/08_loesungen/02/go.mod new file mode 100644 index 0000000..bdf4f1f --- /dev/null +++ b/web/08/labor/08_loesungen/02/go.mod @@ -0,0 +1,5 @@ +module uebung03/cookies + +go 1.24.5 + +require github.com/google/uuid v1.6.0 diff --git a/web/08/labor/08_loesungen/02/go.sum b/web/08/labor/08_loesungen/02/go.sum new file mode 100644 index 0000000..7790d7c --- /dev/null +++ b/web/08/labor/08_loesungen/02/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/web/08/labor/08_loesungen/02/uebung02.go b/web/08/labor/08_loesungen/02/uebung02.go new file mode 100644 index 0000000..9589ce1 --- /dev/null +++ b/web/08/labor/08_loesungen/02/uebung02.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "log" + "net/http" + + "github.com/google/uuid" +) + +func createCookie(w http.ResponseWriter, r *http.Request) { + uuid := uuid.New().String() + cookie := &http.Cookie{ + Name: "keks", + Value: uuid, + } + http.SetCookie(w, cookie) + fmt.Fprintf(w, "Cookie gesetzt: keks = %s\n", uuid) +} + +func showCookie(w http.ResponseWriter, r *http.Request) { + cookie, err := r.Cookie("keks") + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + fmt.Fprintf(w, "Cookie gefunden: keks = %s\n", cookie.Value) +} + +func deleteCookie(w http.ResponseWriter, r *http.Request) { + cookie, err := r.Cookie("keks") + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + cookie.MaxAge = -1 + http.SetCookie(w, cookie) + fmt.Fprintf(w, "Cookie gelöscht.\n") +} + +func main() { + http.HandleFunc("/create-cookie", createCookie) + http.HandleFunc("/show-cookie", showCookie) + http.HandleFunc("/delete-cookie", deleteCookie) + log.Fatal(http.ListenAndServe("localhost:8080", nil)) +} diff --git a/web/08/labor/08_loesungen/03/go.mod b/web/08/labor/08_loesungen/03/go.mod new file mode 100644 index 0000000..4e61f87 --- /dev/null +++ b/web/08/labor/08_loesungen/03/go.mod @@ -0,0 +1,5 @@ +module uebung04/sessions + +go 1.24.5 + +require github.com/google/uuid v1.6.0 diff --git a/web/08/labor/08_loesungen/03/go.sum b/web/08/labor/08_loesungen/03/go.sum new file mode 100644 index 0000000..7790d7c --- /dev/null +++ b/web/08/labor/08_loesungen/03/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/web/08/labor/08_loesungen/03/uebung03.go b/web/08/labor/08_loesungen/03/uebung03.go new file mode 100644 index 0000000..46cc4fb --- /dev/null +++ b/web/08/labor/08_loesungen/03/uebung03.go @@ -0,0 +1,75 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "sync" + + "github.com/google/uuid" +) + +type UserData struct { + Username string `json:"username"` + Nickname string `json:"nickname"` + Admin bool `json:"admin"` +} + +var ( + sessions = make(map[string]UserData) + mu sync.Mutex +) + +func checkSessionID(r *http.Request) (string, error) { + cookie, err := r.Cookie("session_id") + if err != nil { + return "", errors.New("keine session-ID") + } + return cookie.Value, nil +} + +func signup(w http.ResponseWriter, r *http.Request) { + var user UserData + err := json.NewDecoder(r.Body).Decode(&user) + if err != nil || user.Nickname == "" || user.Username == "" { + http.Error(w, "Daten konnten nicht verarbeitet werden.", http.StatusBadRequest) + return + } + + newID := uuid.New().String() + newCookie := http.Cookie{ + Name: "session_id", + Value: newID, + } + http.SetCookie(w, &newCookie) + + mu.Lock() + sessions[newID] = user + mu.Unlock() +} + +func whoami(w http.ResponseWriter, r *http.Request) { + sessionID, err := checkSessionID(r) + if err != nil { + fmt.Fprintf(w, "Du bist noch niemand.") + } else { + mu.Lock() + user, ok := sessions[sessionID] + mu.Unlock() + if ok { + fmt.Fprintf(w, "Hallo %s!\n", user.Nickname) + if user.Admin { + fmt.Fprintln(w, "Du bist als Admin registriert.") + } else { + fmt.Fprintln(w, "Du bist nicht als Admin registriert.") + } + } + } +} + +func main() { + http.HandleFunc("/signup", signup) + http.HandleFunc("/whoami", whoami) + http.ListenAndServe("localhost:8080", nil) +} \ No newline at end of file diff --git a/web/08/labor/08_loesungen/uebung01.go b/web/08/labor/08_loesungen/uebung01.go new file mode 100644 index 0000000..6d9fb90 --- /dev/null +++ b/web/08/labor/08_loesungen/uebung01.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "net/http" +) + +func loginFunc(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Sie wurden erfolgreich eingeloggt.") +} + +func logoutFunc(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Sie wurden erfolgreich ausgeloggt") +} + +func main() { + http.HandleFunc("/login", loginFunc) + http.HandleFunc("/logout", logoutFunc) + http.ListenAndServe("localhost:8080", nil) +} diff --git a/web/09/demos/db/sql/personen/create_personen.sql b/web/09/demos/db/sql/personen/create_personen.sql new file mode 100644 index 0000000..256f6b5 --- /dev/null +++ b/web/09/demos/db/sql/personen/create_personen.sql @@ -0,0 +1,4 @@ +CREATE TABLE personen ( + name TEXT NOT NULL, + hobbies TEXT +); \ No newline at end of file diff --git a/web/09/demos/db/sql/personen/insert_personen.sql b/web/09/demos/db/sql/personen/insert_personen.sql new file mode 100644 index 0000000..83293bf --- /dev/null +++ b/web/09/demos/db/sql/personen/insert_personen.sql @@ -0,0 +1 @@ +INSERT INTO personen (name, hobbies) VALUES ('Siri', 'Lesen, Wandern, Kochen'),('Alexa', 'Kochen, Gitarre spielen'); \ No newline at end of file diff --git a/web/09/demos/db/sql/personen/select_personen.sql b/web/09/demos/db/sql/personen/select_personen.sql new file mode 100644 index 0000000..6aa87aa --- /dev/null +++ b/web/09/demos/db/sql/personen/select_personen.sql @@ -0,0 +1 @@ +SELECT * FROM personen; \ No newline at end of file diff --git a/web/09/demos/hobbies/01_save_db.go b/web/09/demos/hobbies/01_save_db.go new file mode 100644 index 0000000..73fd09d --- /dev/null +++ b/web/09/demos/hobbies/01_save_db.go @@ -0,0 +1,86 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "net/http" + + _ "github.com/lib/pq" +) + +type formExampleDBHandler int + +func (formHandler formExampleDBHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + + //Datenbank-Verbindung herstellen + + // Verbindungszeichenfolge: Benutzername, Passwort, DB-Name, Host, Port + connStr := "user=devuser password=devpass dbname=devdb host=localhost port=5432 sslmode=disable" + + // Verbindung zur Datenbank öffnen + db, err := sql.Open("postgres", connStr) + if err != nil { + log.Println("Fehler beim Öffnen der Verbindung:", err) + http.Error(w, "Interner Server-Fehler.", http.StatusInternalServerError) + return + } + defer db.Close() + + // Verbindung testen + err = db.Ping() + if err != nil { + log.Println("Datenbank nicht erreichbar:", err) + http.Error(w, "Interner Server-Fehler.", http.StatusInternalServerError) + return + } + log.Println("Verbindung erfolgreich hergestellt.") + + // INSERT-Anweisung vorbereiten + query := `INSERT INTO personen (name, hobbies) VALUES ($1, $2)` + // INSERT-Anweisung ausführen + _, err = db.Exec(query, "Louise", "Lesen, Wandern") + if err != nil { + log.Println("Fehler beim Einfügen:", err) + http.Error(w, "Interner Server-Fehler.", http.StatusInternalServerError) + return + } + log.Println("Datensatz erfolgreich eingefügt.") + + // SELECT-Abfrage vorbereiten + rows, err := db.Query(`SELECT name, hobbies FROM personen`) + if err != nil { + log.Println("Fehler bei der Abfrage:", err) + http.Error(w, "Interner Server-Fehler.", http.StatusInternalServerError) + return + } + defer rows.Close() + + // Ergebnisse durchlaufen + for rows.Next() { + var name string + var hobbies string + + err := rows.Scan(&name, &hobbies) + if err != nil { + log.Println("Fehler beim Auslesen:", err) + http.Error(w, "Interner Server-Fehler.", http.StatusInternalServerError) + return + } + + fmt.Printf("Name: %s, Hobbies: %s\n", name, hobbies) + } + + // Fehler beim Iterieren prüfen + if err = rows.Err(); err != nil { + log.Println(err) + http.Error(w, "Interner Server-Fehler.", http.StatusInternalServerError) + return + } + +} + +func main() { + var r formExampleDBHandler + log.Fatal(http.ListenAndServe("localhost:8080", r)) +} diff --git a/web/09/labor/09_aufgaben.md b/web/09/labor/09_aufgaben.md new file mode 100644 index 0000000..2e01283 --- /dev/null +++ b/web/09/labor/09_aufgaben.md @@ -0,0 +1,41 @@ +# Übungsblatt 09 + +## 1. Go-Übung: Datenbank INSERT + +**Aufgabenstellung**: Erstellen Sie ein Webserver-Programm in Go, dass beim Aufruf eines Endpunkts `/insert` einen neuen Datensatz in der Datenbank speichert. + +## 2. Go-Übung: Datenbank UPDATE + +**Aufgabenstellung**: Erweitern Sie das Programm aus Aufgabe 1 um einen neuen Endpunkt `/update`, der einen vorhandenen Datensatz in der Datenbank aktualisiert. + +### Hinweise zu Aufgaben 1 und 2 +1. Erstellen Sie zunächst die benötigten Tabellen und Datensätze in Ihrer Datenbank. Eine Anleitung hierfür finden Sie hier: [Datenbank vorbereiten](db/anleitung_db.md) +2. Verwenden Sie folgende Schnittstellen-Definition für Ihre Implementierung: [Swagger-API](hobbiesapi.json) +3. Tipp: Um die Elemente eines Slice in einem String zusammenzufügen, können Sie das Paket `strings` aus der Standard-Bibliothek verwenden: [strings.Join](https://pkg.go.dev/strings#Join) + +## 3. HTML-Übung: Formular + +**Aufgabenstellung**: Schreiben Sie ein HTML-Formular, dass die Schnittstelle [Swagger-API](hobbiesapi.json) implementiert und Benutzereingaben an das in Aufgabe 1 + 2 implementierte Backend sendet. + +## 4. Go-Übung: CORS + +_Keine Abgabe erforderlich_ + +**Aufgabenstellung**: Erweitern Sie die beiden serverseitigen Go-Programme zur Workshop-Anmeldung aus den vorangegangen Übungsblättern 07 und 08 so, dass beim Ausführen der "Try Out"-Funktion in der SwaggerUI-Preview kein CORS-Fehler mehr auftaucht. + +#### Arbeitsschritte + +1. Fügen Sie folgende Zeile am Anfang in Ihrer Handler `func` oder in der `ServeHTTP`-Funktion ihres `http.Handler` auf: + + ```go + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusNoContent) + return + } + ``` + +2. Testen Sie Ihr(e) Programm(e) mit dieser [Workshop-API](workshop-api.json). \ No newline at end of file diff --git a/web/09/labor/db/anleitung_db.md b/web/09/labor/db/anleitung_db.md new file mode 100644 index 0000000..04f1759 --- /dev/null +++ b/web/09/labor/db/anleitung_db.md @@ -0,0 +1,23 @@ +# Tooling für die Entwicklung mit einer PostGreSQL-DB + +Diese Anleitung beschreibt die Schritte zur Verbindung mit einer PostGreSQL-DB sowie das Erstellen von Tabellen mit Hilfe der VS Code Extension _PostgreSQL_ und der im Dev-Container enthaltenen Datenbank. + +## Neue Verbindung + +1. Prüfen Sie, ob Sie sich im Dev-Container befinden +2. Klicken Sie auf das PostGreSQL-Symbol (VS Code Extension) in der Activity Bar (default: linke Leiste) +3. Wählen Sie "Add New Connection" und "CONNECTIONS" in der Primary Side Bar (meistens: links) + 1. Füllen Sie die geforderten Felder aus. Sie finden die Daten zu Ihrer Datenbank-Installation in der Datei `.env` innerhalb des `.devcontainer`- Ordners. + 2. Testen Sie die Verbindung ("Test Connection") + 3. Vergeben Sie einen geeigneten Namen und speichern Sie die Verbindung ("Save & Connect") + +## Tabellen +4. Öffnen Sie die Datei `create_personen.sql`. +5. Auf der rechten Seite der Tab-Leiste erscheinen nun mehrere Symbole. Wählen Sie "Connect" aus und geben Sie ggf. Ihr Datenbank-Passwort ein (`POSTGRES_PASSWORD` in `.env`) +6. Wählen Sie nun das Symbol für "Change PostGreSQL Database" aus und wählen Ihre Datenbank aus (`POSTGRES_DB` in `.env`) +7. Wählen Sie nun das Symbol für "Execute PostGreSQL Query" aus: Es wird eine neue Tabelle in Ihrer Datenbank erstellt. + +## Einfügen und Anzeigen +8. Führen Sie die Schritte 5.-7. nun auch für die Dateien `insert_personen.sql` und `select_personen.sql` durch. +9. Alternativ oder zusätzlich können Sie natürlich auch eigene Queries ausführen. + diff --git a/web/09/labor/db/create_personen.sql b/web/09/labor/db/create_personen.sql new file mode 100644 index 0000000..27ac3bb --- /dev/null +++ b/web/09/labor/db/create_personen.sql @@ -0,0 +1,5 @@ +CREATE TABLE personen ( + name TEXT NOT NULL, + hobbies TEXT, + alter SMALLINT +); \ No newline at end of file diff --git a/web/09/labor/db/insert_personen.sql b/web/09/labor/db/insert_personen.sql new file mode 100644 index 0000000..987a7a6 --- /dev/null +++ b/web/09/labor/db/insert_personen.sql @@ -0,0 +1 @@ +INSERT INTO personen (name, hobbies, alter) VALUES ('Siri', 'Lesen, Wandern, Kochen', 14),('Alexa', 'Kochen, Gitarre spielen', 11); \ No newline at end of file diff --git a/web/09/labor/db/select_personen.sql b/web/09/labor/db/select_personen.sql new file mode 100644 index 0000000..6aa87aa --- /dev/null +++ b/web/09/labor/db/select_personen.sql @@ -0,0 +1 @@ +SELECT * FROM personen; \ No newline at end of file diff --git a/web/09/labor/hobbiesapi.json b/web/09/labor/hobbiesapi.json new file mode 100644 index 0000000..46b295c --- /dev/null +++ b/web/09/labor/hobbiesapi.json @@ -0,0 +1,129 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Hobbies API", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:8080" + } + ], + "paths": { + "/insert": { + "post": { + "summary": "Erstellt eine:n neue:n Benutzer:in mit einer Liste von Hobbies", + "requestBody": { + "description": "Name und Hobbies des neuen Benutzers/der neuen Benutzerin", + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Hobbies" + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/SuccessResponse" + }, + "400": { + "$ref": "#/components/responses/BadRequestResponse" + }, + "405": { + "$ref": "#/components/responses/MethodNotAllowedResponse" + } + } + } + }, + "/update": { + "post": { + "summary": "Aktualisiert eine:n vorhandene:n Benutzer:in mit einer Liste von Hobbies", + "requestBody": { + "description": "Name und Hobbies des/der vorhandenen Benutzer:in", + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Hobbies" + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/SuccessResponse" + }, + "400": { + "$ref": "#/components/responses/BadRequestResponse" + }, + "405": { + "$ref": "#/components/responses/MethodNotAllowedResponse" + } + } + } + } + }, + "components": { + "schemas": { + "Hobbies": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "Lea" + }, + "hobby": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "reiten", + "backen" + ] + }, + "alter": { + "type": "integer", + "example": 23 + } + } + } + }, + "responses": { + "SuccessResponse": { + "description": "Success", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "BadRequestResponse": { + "description": "Bad Request", + "content": { + "text/html": { + "schema": { + "type": "string", + "example": "Kein POST-Formular gesendet" + } + } + } + }, + "MethodNotAllowedResponse": { + "description": "Method not allowed", + "content": { + "text/html": { + "schema": { + "type": "string", + "example": "Nur POST-Methode erlaubt." + } + } + } + } + } + } +} \ No newline at end of file diff --git a/web/09/labor/workshop-api.json b/web/09/labor/workshop-api.json new file mode 100644 index 0000000..60c76d7 --- /dev/null +++ b/web/09/labor/workshop-api.json @@ -0,0 +1,119 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Workshop API", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:8080" + } + ], + "paths": { + "/registrierung": { + "post": { + "summary": "Sends all information needed for registering at a workshop", + "requestBody": { + "required": false, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/RegistrierungForm" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegistrierungForm" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "text/plain": { + "schema": { + "type": "string", + "example": "Kein Formular gesendet" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "RegistrierungForm": { + "type": "object", + "required": [ + "vorname", + "nachname", + "agb", + "format" + ], + "properties": { + "vorname": { + "type": "string", + "example": "Zoya" + }, + "nachname": { + "type": "string", + "example": "Akhtar" + }, + "email": { + "type": "string", + "example": "z.akhtar@test.de" + }, + "telefon": { + "type": "string", + "example": "Zoya" + }, + "sessions": { + "type": "array", + "items": { + "type": "string", + "example": "vormittag" + }, + "example": [ + "vormittag", + "nachmittag" + ] + }, + "agb": { + "type": "string", + "enum": ["ja"], + "example": "ja" + }, + "newsletter": { + "type": "string", + "enum": ["ja"], + "example": "ja" + }, + "equipment": { + "type": "string", + "enum": ["ja"], + "example": "ja" + }, + "format": { + "type": "string", + "enum": ["online","praesenz"], + "example": "online" + } + } + } + } + } +} \ No newline at end of file diff --git a/web/10/demos/01_hello.html b/web/10/demos/01_hello.html new file mode 100644 index 0000000..6db8560 --- /dev/null +++ b/web/10/demos/01_hello.html @@ -0,0 +1,15 @@ + + + +
+ + + +