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.
This commit is contained in:
parent
4dd9c18832
commit
a408e1487d
12 changed files with 400 additions and 544 deletions
192
forms.html
192
forms.html
|
|
@ -1,192 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>D&D Monster Form</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@2.31.0/dist/full.css" rel="stylesheet" type="text/css">
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
font-family: 'arial', sans-serif;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.form-box {
|
||||
max-width: 700px;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.form-item2 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-100 h-screen flex items-center justify-center">
|
||||
|
||||
<img src="images/banner.jpg">
|
||||
<div data-theme="dark" class="bg-white p-6 rounded-lg shadow-md w-full max-w-lg">
|
||||
<div class="navbar bg-base-100">
|
||||
<a class="btn btn-ghost text-xl">Dungeons & Dragons</a>
|
||||
</div>
|
||||
<div class="container mx-auto card w-96 bg-base-100 shadow-xl">
|
||||
<div class="card-body form-box">
|
||||
<h2 class="card-title">Monster Form</h2>
|
||||
<form action="/submit" method="post"
|
||||
class="grid grid-columns-2 gap-4 space-y-4 grid h-screen place-items-center">
|
||||
<div class="form-item">
|
||||
<label for="filename">Filename:</label>
|
||||
<input type="text" name="filename" required placeholder="Dateiname"
|
||||
class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="name">Monster Name:</label>
|
||||
<input type="text" name="name" required placeholder="Type here"
|
||||
class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="source">Monster Source:</label>
|
||||
<input type="text" name="source" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="size">Size:</label>
|
||||
<input type="text" name="size" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="type">Type:</label>
|
||||
<input type="text" name="type" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="alignment">Alignment:</label>
|
||||
<input type="text" name="alignment" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="ac">AC:</label>
|
||||
<input type="number" name="ac" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="acFrom">AC From:</label>
|
||||
<input type="text" name="acFrom" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="hpAverage">HP Average:</label>
|
||||
<input type="number" name="hpAverage" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="hpFormula">HP Formula:</label>
|
||||
<input type="text" name="hpFormula" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="speed">Speed:</label>
|
||||
<input type="number" name="speed" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="str">Str:</label>
|
||||
<input type="number" name="str" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="dex">Dex:</label>
|
||||
<input type="number" name="dex" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="con">Con:</label>
|
||||
<input type="number" name="con" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="int">Int:</label>
|
||||
<input type="number" name="int" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="wis">Wis:</label>
|
||||
<input type="number" name="wis" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="cha">Cha:</label>
|
||||
<input type="number" name="cha" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="saveDex">Save Dex:</label>
|
||||
<input type="text" name="saveDex" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="saveCon">Save Con:</label>
|
||||
<input type="text" name="saveCon" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="saveWis">Save Wis:</label>
|
||||
<input type="text" name="saveWis" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="perception">Perception:</label>
|
||||
<input type="text" name="perception" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="stealth">Stealth:</label>
|
||||
<input type="text" name="stealth" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="damageRes">Damage Resistances:</label>
|
||||
<input type="text" name="damageRes" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="senses">Senses:</label>
|
||||
<input type="text" name="senses" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="languages">Languages:</label>
|
||||
<input type="text" name="languages" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="cr">CR:</label>
|
||||
<input type="text" name="cr" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="traitName">Trait Name:</label>
|
||||
<input type="text" name="traitName" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="traitEntry">Trait Entry:</label>
|
||||
<input type="text" name="traitEntry" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="actionName">Action Name:</label>
|
||||
<input type="text" name="actionName" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="actionEntry">Action Entry:</label>
|
||||
<input type="text" name="actionEntry" required class="input input-bordered w-full max-w-xs"><br>
|
||||
</div>
|
||||
<input type="hidden" name="filename" value="{{.Filename}}">
|
||||
<div class="form-item2">
|
||||
<input type="submit" value="Submit" class="btn">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
26
handlers/about_handler.go
Normal file
26
handlers/about_handler.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
85
handlers/add_monster_handler.go
Normal file
85
handlers/add_monster_handler.go
Normal file
|
|
@ -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
|
||||
}
|
||||
26
handlers/contact_handler.go
Normal file
26
handlers/contact_handler.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
26
handlers/form_handler.go
Normal file
26
handlers/form_handler.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
65
handlers/submit_handler.go
Normal file
65
handlers/submit_handler.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
351
main.go
351
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
140
model/model.go
Normal file
140
model/model.go
Normal file
|
|
@ -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
|
||||
}
|
||||
2
templates/about.html
Normal file
2
templates/about.html
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
{{ define "about" }}
|
||||
{{ end }}
|
||||
|
|
@ -6,7 +6,6 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.Title}}</title>
|
||||
<!-- Hier könnten weitere Meta-Tags, Stylesheets, JavaScript-Dateien usw. eingefügt werden -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@2.31.0/dist/full.css" rel="stylesheet" type="text/css">
|
||||
<script src="https://unpkg.com/htmx.org@1.9.9"
|
||||
|
|
@ -48,6 +47,18 @@
|
|||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.banner {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.centered {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
|
@ -59,7 +70,6 @@
|
|||
|
||||
{{ template "footer" . }}
|
||||
|
||||
<!-- Hier könnten weitere Scripts eingefügt werden, z.B., htmx und andere Abhängigkeiten -->
|
||||
|
||||
</body>
|
||||
|
||||
|
|
|
|||
2
templates/contact.html
Normal file
2
templates/contact.html
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
{{ define "contact" }}
|
||||
{{ end }}
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
{{ define "header" }}
|
||||
<header>
|
||||
<div id="banner" class="bg-gray-800 p-8 text-white">
|
||||
<!-- Hier könntest du ein Dungeons-and-Dragons-Banner einfügen -->
|
||||
<nav class="navbar bg-base-100">
|
||||
<a href="/" class="btn btn-ghost text-xl">Dungeons & Dragons</a>
|
||||
<a href="/about" class="btn btn-ghost text-xl">About</a>
|
||||
<a href="/contact" class="btn btn-ghost text-xl">Contact</a>
|
||||
</nav>
|
||||
<div id="banner" class="banner bg-gray-800 p-8 text-white">
|
||||
<img src="/images/images/banner.jpg" alt="Dungeons-and-Dragons-Banner">
|
||||
<a href="https://dnd.wizards.com/resources/press-assets">image credits</a>
|
||||
<h1 class="text-4xl font-bold">Dungeons and Dragons</h1>
|
||||
<h1 class="centered text-4xl font-bold">Dungeons and Dragons</h1>
|
||||
</div>
|
||||
<nav class="navbar bg-base-100">
|
||||
<a href="#" class="btn btn-ghost text-xl">Dungeons & Dragons</a>
|
||||
<a href="#" class="btn btn-ghost text-xl">About</a>
|
||||
<a href="#" class="btn btn-ghost text-xl">Contact</a>
|
||||
</nav>
|
||||
</header>
|
||||
{{ end }}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue