From a408e1487d61f528df683219669871f77305a3f6 Mon Sep 17 00:00:00 2001 From: Patryk Hegenberg Date: Mon, 4 Dec 2023 11:04:06 +0100 Subject: [PATCH] restructured and refactored codebase For better code organisation the entire Codebase has been restructured and cleaned up. handlers have been separated into an own package, as well as the model. --- forms.html | 192 ----------------- handlers/about_handler.go | 26 +++ handlers/add_monster_handler.go | 85 ++++++++ handlers/contact_handler.go | 26 +++ handlers/form_handler.go | 26 +++ handlers/submit_handler.go | 65 ++++++ main.go | 351 +------------------------------- model/model.go | 140 +++++++++++++ templates/about.html | 2 + templates/base.html | 14 +- templates/contact.html | 2 + templates/header.html | 15 +- 12 files changed, 400 insertions(+), 544 deletions(-) delete mode 100644 forms.html create mode 100644 handlers/about_handler.go create mode 100644 handlers/add_monster_handler.go create mode 100644 handlers/contact_handler.go create mode 100644 handlers/form_handler.go create mode 100644 handlers/submit_handler.go create mode 100644 model/model.go create mode 100644 templates/about.html create mode 100644 templates/contact.html diff --git a/forms.html b/forms.html deleted file mode 100644 index cff32e1..0000000 --- a/forms.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - D&D Monster Form - - - - - - - -
- -
-
-

Monster Form

-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
- -
- -
-
-
-
-
- - - diff --git a/handlers/about_handler.go b/handlers/about_handler.go new file mode 100644 index 0000000..83e2b6c --- /dev/null +++ b/handlers/about_handler.go @@ -0,0 +1,26 @@ +package handlers + +import ( + "embed" + "html/template" + "log" + "net/http" +) + +func AboutHandler(content embed.FS) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + tmpl, err := template.ParseFS(content, "templates/base.html", "templates/header.html", "templates/main.html", "templates/footer.html", "templates/about.html") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + err = tmpl.ExecuteTemplate(w, "about", map[string]interface{}{ + "Title": "Dungeons & Dragons Monster Generator", + }) + if err != nil { + log.Printf("Template execution error: %v\n", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} diff --git a/handlers/add_monster_handler.go b/handlers/add_monster_handler.go new file mode 100644 index 0000000..222e7f4 --- /dev/null +++ b/handlers/add_monster_handler.go @@ -0,0 +1,85 @@ +package handlers + +import ( + "ddServer/model" + "net/http" + "strconv" +) + +func AddMonster(Monsters *[]model.Monster) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // TODO + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + err := r.ParseForm() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + monster := model.Monster{ + Name: r.FormValue("name"), + Source: r.FormValue("source"), + Size: []string{r.FormValue("size")}, + Type: r.FormValue("type"), + Alignment: []string{r.FormValue("alignment")}, + AC: []model.AC{ + { + AC: parseInt(r.FormValue("ac")), + From: []string{r.FormValue("acFrom")}, + }, + }, + HP: model.HP{ + Average: parseInt(r.FormValue("hpAverage")), + Formula: r.FormValue("hpFormula"), + }, + Speed: model.Speed{ + Walk: parseInt(r.FormValue("speed")), + }, + Str: parseInt(r.FormValue("str")), + Dex: parseInt(r.FormValue("dex")), + Con: parseInt(r.FormValue("con")), + Int: parseInt(r.FormValue("int")), + Wis: parseInt(r.FormValue("wis")), + Cha: parseInt(r.FormValue("cha")), + Save: model.Save{ + Dex: r.FormValue("saveDex"), + Con: r.FormValue("saveCon"), + Wis: r.FormValue("saveWis"), + }, + Skill: model.Skill{ + Perception: r.FormValue("perception"), + Stealth: r.FormValue("stealth"), + }, + DamageRes: []string{r.FormValue("damageRes")}, + Senses: []string{r.FormValue("senses")}, + Languages: []string{r.FormValue("languages")}, + CR: r.FormValue("cr"), + Traits: []model.Trait{ + { + Name: r.FormValue("traitName"), + Entries: []string{r.FormValue("traitEntry")}, + }, + }, + Actions: []model.Action{ + { + Name: r.FormValue("actionName"), + Entries: []string{r.FormValue("actionEntry")}, + }, + }, + } + *Monsters = append(*Monsters, monster) + } +} + +// parseInt konvertiert einen String zu einem Integer und gibt 0 zurück, wenn die Konvertierung fehlschlägt +func parseInt(s string) int { + i, err := strconv.Atoi(s) + if err != nil { + return 0 + } + return i +} diff --git a/handlers/contact_handler.go b/handlers/contact_handler.go new file mode 100644 index 0000000..407fe75 --- /dev/null +++ b/handlers/contact_handler.go @@ -0,0 +1,26 @@ +package handlers + +import ( + "embed" + "html/template" + "log" + "net/http" +) + +func ContactHandler(content embed.FS) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + tmpl, err := template.ParseFS(content, "templates/base.html", "templates/header.html", "templates/main.html", "templates/footer.html", "templates/about.html", "templates/contact.html") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + err = tmpl.ExecuteTemplate(w, "contact", map[string]interface{}{ + "Title": "Dungeons & Dragons Monster Generator", + }) + if err != nil { + log.Printf("Template execution error: %v\n", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} diff --git a/handlers/form_handler.go b/handlers/form_handler.go new file mode 100644 index 0000000..dc57041 --- /dev/null +++ b/handlers/form_handler.go @@ -0,0 +1,26 @@ +package handlers + +import ( + "embed" + "html/template" + "log" + "net/http" +) + +func FormHandler(content embed.FS, filename string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + tmpl, err := template.ParseFS(content, "templates/base.html", "templates/header.html", "templates/main.html", "templates/footer.html", "templates/monsterForm.html") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + err = tmpl.ExecuteTemplate(w, "base", map[string]interface{}{ + "Title": "Dungeons & Dragons Monster Generator", + }) + if err != nil { + log.Printf("Template execution error: %v\n", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} diff --git a/handlers/submit_handler.go b/handlers/submit_handler.go new file mode 100644 index 0000000..744b879 --- /dev/null +++ b/handlers/submit_handler.go @@ -0,0 +1,65 @@ +package handlers + +import ( + "ddServer/model" + "embed" + "encoding/json" + "fmt" + "net/http" + "os" + "sync" +) + +var mu sync.Mutex + +// submitHandler verarbeitet die Formulardaten +func SubmitHandler(content embed.FS, chars *[]model.Character, Monsters *[]model.Monster, filename string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // Formulardaten parsen + err := r.ParseForm() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Monster-Objekt erstellen + filename := r.FormValue("filename") + + // Charakter-Objekt erstellen oder aktualisieren + mu.Lock() + defer mu.Unlock() + + char := model.GetOrCreateCharacter(filename, *chars) + char.Monster = append(char.Monster, *Monsters...) + + // Charakterdaten in JSON umwandeln + charJSON, err := json.Marshal(char) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // JSON-Daten in die Datei schreiben + err = model.WriteToFile(filename, charJSON) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + // Dateiinhalt lesen + fileContent, err := os.ReadFile(filename) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Datei zum Download anbieten + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) + w.Header().Set("Content-Type", "application/json") + w.Write(fileContent) + } +} diff --git a/main.go b/main.go index d2f8ba3..e91edee 100644 --- a/main.go +++ b/main.go @@ -1,366 +1,33 @@ package main import ( + "ddServer/handlers" + "ddServer/model" "embed" - "encoding/json" "fmt" - "html/template" "net/http" - "os" - "strconv" "sync" - "time" ) -// Monster struct für die Daten des Monsters -type Monster struct { - Name string `json:"name"` - Source string `json:"source"` - Size []string `json:"size"` - Type string `json:"type"` - Alignment []string `json:"alignment"` - AC []AC `json:"ac"` - HP HP `json:"hp"` - Speed Speed `json:"speed"` - Save Save `json:"save"` - Skill Skill `json:"skill"` - DamageRes []string `json:"damageResistances"` - Senses []string `json:"senses"` - Languages []string `json:"languages"` - CR string `json:"cr"` - Traits []Trait `json:"trait"` - Actions []Action `json:"action"` - Str int `json:"str"` - Dex int `json:"dex"` - Con int `json:"con"` - Int int `json:"int"` - Wis int `json:"wis"` - Cha int `json:"cha"` -} - -type AC struct { - AC int `json:"ac"` - From []string `json:"from"` -} - -type HP struct { - Average int `json:"average"` - Formula string `json:"formula"` -} - -type Speed struct { - Walk int `json:"walk"` -} - -type Save struct { - Dex string `json:"dex"` - Con string `json:"con"` - Wis string `json:"wis"` -} - -type Skill struct { - Perception string `json:"perception"` - Stealth string `json:"stealth"` -} - -type Trait struct { - Name string `json:"name"` - Entries []string `json:"entries"` -} - -type Action struct { - Name string `json:"name"` - Entries []string `json:"entries"` -} - -// Character struct für die Daten des Charakters -type Character struct { - Meta Meta `json:"_meta"` - Monster []Monster `json:"monster"` -} - -// Meta struct für Meta-Informationen -type Meta struct { - Sources []Source `json:"sources"` - DateAdded int64 `json:"dateAdded"` - DateLastModified int64 `json:"dateLastModified"` - DateLastModifiedHash string `json:"_dateLastModifiedHash"` -} - -type Source struct { - Json string `json:"json"` - Abbreviation string `json:"abbreviation"` - Authors []string `json:"authors"` - ConvertedBy []string `json:"convertedBy"` - Version string `json:"version"` -} - var ( mu sync.Mutex - chars []Character + chars []model.Character //go:embed templates/*.html //go:embed images/* content embed.FS - Monsters []Monster + Monsters []model.Monster ) func main() { filename := "" - http.HandleFunc("/", formHandler(filename)) - http.HandleFunc("/submit", submitHandler(filename)) + http.HandleFunc("/", handlers.FormHandler(content, filename)) + http.HandleFunc("/submit", handlers.SubmitHandler(content, &chars, &Monsters, filename)) http.Handle("/images/", http.StripPrefix("/images/", http.FileServer(http.FS(content)))) - http.HandleFunc("/addMonster", addMonster()) + http.HandleFunc("/addMonster", handlers.AddMonster(&Monsters)) + http.HandleFunc("/about", handlers.AboutHandler(content)) + http.HandleFunc("/contact", handlers.ContactHandler(content)) fmt.Println("Server gestartet, erreichbar unter http://localhost:8080") http.ListenAndServe(":8080", nil) } - -func formHandler(filename string) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - tmpl, err := template.ParseFS(content, "templates/base.html", "templates/header.html", "templates/main.html", "templates/footer.html", "templates/monsterForm.html") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - err = tmpl.ExecuteTemplate(w, "base", map[string]interface{}{ - "Title": "Dungeons & Dragons Monster Generator", - }) - if err != nil { - fmt.Println("Template execution error:", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - } - } -} - -// submitHandler verarbeitet die Formulardaten -func submitHandler(filename string) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - // Formulardaten parsen - err := r.ParseForm() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Monster-Objekt erstellen - filename := r.FormValue("filename") - /*monster := Monster{ - Name: r.FormValue("name"), - Source: r.FormValue("source"), - Size: []string{r.FormValue("size")}, - Type: r.FormValue("type"), - Alignment: []string{r.FormValue("alignment")}, - AC: []AC{ - { - AC: parseInt(r.FormValue("ac")), - From: []string{r.FormValue("acFrom")}, - }, - }, - HP: HP{ - Average: parseInt(r.FormValue("hpAverage")), - Formula: r.FormValue("hpFormula"), - }, - Speed: Speed{ - Walk: parseInt(r.FormValue("speed")), - }, - Str: parseInt(r.FormValue("str")), - Dex: parseInt(r.FormValue("dex")), - Con: parseInt(r.FormValue("con")), - Int: parseInt(r.FormValue("int")), - Wis: parseInt(r.FormValue("wis")), - Cha: parseInt(r.FormValue("cha")), - Save: Save{ - Dex: r.FormValue("saveDex"), - Con: r.FormValue("saveCon"), - Wis: r.FormValue("saveWis"), - }, - Skill: Skill{ - Perception: r.FormValue("perception"), - Stealth: r.FormValue("stealth"), - }, - DamageRes: []string{r.FormValue("damageRes")}, - Senses: []string{r.FormValue("senses")}, - Languages: []string{r.FormValue("languages")}, - CR: r.FormValue("cr"), - Traits: []Trait{ - { - Name: r.FormValue("traitName"), - Entries: []string{r.FormValue("traitEntry")}, - }, - }, - Actions: []Action{ - { - Name: r.FormValue("actionName"), - Entries: []string{r.FormValue("actionEntry")}, - }, - }, - }*/ - - // Charakter-Objekt erstellen oder aktualisieren - mu.Lock() - defer mu.Unlock() - - char := getOrCreateCharacter(filename) - char.Monster = append(char.Monster, Monsters...) - - // Charakterdaten in JSON umwandeln - charJSON, err := json.Marshal(char) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // JSON-Daten in die Datei schreiben - err = writeToFile(filename, charJSON) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - // Dateiinhalt lesen - fileContent, err := os.ReadFile(filename) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Datei zum Download anbieten - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) - w.Header().Set("Content-Type", "application/json") - w.Write(fileContent) - } -} - -// writeToFile schreibt Daten in eine Datei -func writeToFile(filename string, data []byte) error { - file, err := os.Create(filename) - if err != nil { - return err - } - defer file.Close() - - _, err = file.Write(data) - if err != nil { - return err - } - - return nil -} - -// getOrCreateCharacter gibt das aktuelle Charakterobjekt zurück oder erstellt ein neues -func getOrCreateCharacter(filename string) Character { - for _, char := range chars { - if char.Meta.DateLastModified == 0 { - // Ein leeres Charakterobjekt wurde gefunden - return char - } - } - - // Erstelle ein neues Charakterobjekt - now := time.Now().Unix() - newChar := Character{ - Meta: Meta{ - Sources: []Source{ - { - Json: "Malgorgon", - Abbreviation: "MG", - Authors: []string{"Krzysztof"}, - ConvertedBy: []string{"Krzysztof"}, - Version: "unknown", - }, - }, - DateAdded: now, - DateLastModified: now, - DateLastModifiedHash: fmt.Sprintf("%x", now), - }, - Monster: []Monster{}, - } - - chars = append(chars, newChar) - - return newChar -} - -// parseInt konvertiert einen String zu einem Integer und gibt 0 zurück, wenn die Konvertierung fehlschlägt -func parseInt(s string) int { - i, err := strconv.Atoi(s) - if err != nil { - return 0 - } - return i -} - -func addMonster() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - // TODO - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - err := r.ParseForm() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - monster := Monster{ - Name: r.FormValue("name"), - Source: r.FormValue("source"), - Size: []string{r.FormValue("size")}, - Type: r.FormValue("type"), - Alignment: []string{r.FormValue("alignment")}, - AC: []AC{ - { - AC: parseInt(r.FormValue("ac")), - From: []string{r.FormValue("acFrom")}, - }, - }, - HP: HP{ - Average: parseInt(r.FormValue("hpAverage")), - Formula: r.FormValue("hpFormula"), - }, - Speed: Speed{ - Walk: parseInt(r.FormValue("speed")), - }, - Str: parseInt(r.FormValue("str")), - Dex: parseInt(r.FormValue("dex")), - Con: parseInt(r.FormValue("con")), - Int: parseInt(r.FormValue("int")), - Wis: parseInt(r.FormValue("wis")), - Cha: parseInt(r.FormValue("cha")), - Save: Save{ - Dex: r.FormValue("saveDex"), - Con: r.FormValue("saveCon"), - Wis: r.FormValue("saveWis"), - }, - Skill: Skill{ - Perception: r.FormValue("perception"), - Stealth: r.FormValue("stealth"), - }, - DamageRes: []string{r.FormValue("damageRes")}, - Senses: []string{r.FormValue("senses")}, - Languages: []string{r.FormValue("languages")}, - CR: r.FormValue("cr"), - Traits: []Trait{ - { - Name: r.FormValue("traitName"), - Entries: []string{r.FormValue("traitEntry")}, - }, - }, - Actions: []Action{ - { - Name: r.FormValue("actionName"), - Entries: []string{r.FormValue("actionEntry")}, - }, - }, - } - Monsters = append(Monsters, monster) - } -} diff --git a/model/model.go b/model/model.go new file mode 100644 index 0000000..03f2949 --- /dev/null +++ b/model/model.go @@ -0,0 +1,140 @@ +package model + +import ( + "fmt" + "os" + "time" +) + +// Monster struct für die Daten des Monsters +type Monster struct { + Save Save `json:"save"` + Skill Skill `json:"skill"` + HP HP `json:"hp"` + Source string `json:"source"` + CR string `json:"cr"` + Type string `json:"type"` + Name string `json:"name"` + DamageRes []string `json:"damageResistances"` + Traits []Trait `json:"trait"` + AC []AC `json:"ac"` + Alignment []string `json:"alignment"` + Senses []string `json:"senses"` + Languages []string `json:"languages"` + Size []string `json:"size"` + Actions []Action `json:"action"` + Speed Speed `json:"speed"` + Str int `json:"str"` + Dex int `json:"dex"` + Con int `json:"con"` + Int int `json:"int"` + Wis int `json:"wis"` + Cha int `json:"cha"` +} + +type AC struct { + From []string `json:"from"` + AC int `json:"ac"` +} + +type HP struct { + Formula string `json:"formula"` + Average int `json:"average"` +} + +type Speed struct { + Walk int `json:"walk"` +} + +type Save struct { + Dex string `json:"dex"` + Con string `json:"con"` + Wis string `json:"wis"` +} + +type Skill struct { + Perception string `json:"perception"` + Stealth string `json:"stealth"` +} + +type Trait struct { + Name string `json:"name"` + Entries []string `json:"entries"` +} + +type Action struct { + Name string `json:"name"` + Entries []string `json:"entries"` +} + +// Character struct für die Daten des Charakters +type Character struct { + Monster []Monster `json:"monster"` + Meta Meta `json:"_meta"` +} + +// Meta struct für Meta-Informationen +type Meta struct { + DateLastModifiedHash string `json:"_dateLastModifiedHash"` + Sources []Source `json:"sources"` + DateAdded int64 `json:"dateAdded"` + DateLastModified int64 `json:"dateLastModified"` +} + +type Source struct { + Json string `json:"json"` + Abbreviation string `json:"abbreviation"` + Version string `json:"version"` + Authors []string `json:"authors"` + ConvertedBy []string `json:"convertedBy"` +} + +// writeToFile schreibt Daten in eine Datei +func WriteToFile(filename string, data []byte) error { + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + _, err = file.Write(data) + if err != nil { + return err + } + + return nil +} + +// getOrCreateCharacter gibt das aktuelle Charakterobjekt zurück oder erstellt ein neues +func GetOrCreateCharacter(filename string, chars []Character) Character { + for _, char := range chars { + if char.Meta.DateLastModified == 0 { + // Ein leeres Charakterobjekt wurde gefunden + return char + } + } + + // Erstelle ein neues Charakterobjekt + now := time.Now().Unix() + newChar := Character{ + Meta: Meta{ + Sources: []Source{ + { + Json: "Malgorgon", + Abbreviation: "MG", + Authors: []string{"Krzysztof"}, + ConvertedBy: []string{"Krzysztof"}, + Version: "unknown", + }, + }, + DateAdded: now, + DateLastModified: now, + DateLastModifiedHash: fmt.Sprintf("%x", now), + }, + Monster: []Monster{}, + } + + chars = append(chars, newChar) + + return newChar +} diff --git a/templates/about.html b/templates/about.html new file mode 100644 index 0000000..cff355b --- /dev/null +++ b/templates/about.html @@ -0,0 +1,2 @@ +{{ define "about" }} +{{ end }} diff --git a/templates/base.html b/templates/base.html index 0fba542..cbfd77f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -6,7 +6,6 @@ {{.Title}} -