add db.go for database connection

This commit is contained in:
Patryk Hegenberg 2023-12-29 12:26:16 +01:00
parent c2913318de
commit e0dca3bbd8
10 changed files with 413 additions and 0 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
# vscode config files # vscode config files
./.vscode/* ./.vscode/*
math_wizard_db_data/*

138
db/db.go Normal file
View file

@ -0,0 +1,138 @@
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
// Connect to MySQL database.
type Database struct {
connection *sql.DB
statement *sql.Stmt
}
func NewDatabase(config map[string]string, username string, password string) (*Database, error) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", username, password, config["host"], config["port"], config["dbname"])
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
return &Database{
connection: db,
}, nil
}
func (d *Database) Query(query string, params ...interface{}) (*Database, error) {
stmt, err := d.connection.Prepare(query)
if err != nil {
return nil, err
}
d.statement = stmt
_, err = stmt.Exec(params...)
if err != nil {
return nil, err
}
return d, nil
}
func (d *Database) Find() (map[string]interface{}, error) {
result := make(map[string]interface{})
err := d.statement.QueryRow().Scan(result)
if err != nil {
return nil, err
}
return result, nil
}
func (d *Database) FindOrFail() (map[string]interface{}, error) {
result, err := d.Find()
if err != nil {
return nil, err
}
if result == nil {
return nil, fmt.Errorf("Record not found")
}
return result, nil
}
func (d *Database) Get() ([]map[string]interface{}, error) {
rows, err := d.statement.Query()
if err != nil {
return nil, err
}
defer rows.Close()
columns, err := rows.Columns()
if err != nil {
return nil, err
}
result := make([]map[string]interface{}, 0)
values := make([]interface{}, len(columns))
valuePtrs := make([]interface{}, len(columns))
for rows.Next() {
for i := 0; i < len(columns); i++ {
valuePtrs[i] = &values[i]
}
err := rows.Scan(valuePtrs...)
if err != nil {
return nil, err
}
entry := make(map[string]interface{})
for i, col := range columns {
val := values[i]
b, ok := val.([]byte)
if ok {
entry[col] = string(b)
} else {
entry[col] = val
}
}
result = append(result, entry)
}
return result, nil
}
func (d *Database) Delete(params string) (*Database, error) {
_, err := d.Query("DELETE FROM user WHERE username = ?", params)
if err != nil {
return nil, err
}
return d, nil
}
func (d *Database) Update(params map[string]interface{}) (*Database, error) {
_, err := d.Query("UPDATE user SET level = ?, xp = ?, coins = ? WHERE username = ?", params["level"], params["xp"], params["coins"], params["username"])
if err != nil {
return nil, err
}
return d, nil
}
func (d *Database) GetPlayerStats(params string) (map[string]interface{}, error) {
result, err := d.Query("SELECT username, level, lesson_count, xp, coins FROM user WHERE username = ?", params).Get()
if err != nil {
return nil, err
}
if len(result) == 0 {
return nil, fmt.Errorf("Record not found")
}
return result[0], nil
}

23
go.mod Normal file
View file

@ -0,0 +1,23 @@
module the_math_wizard
go 1.21.5
require (
github.com/chasefleming/elem-go v0.17.0
github.com/labstack/echo/v4 v4.11.4
)
require (
github.com/go-sql-driver/mysql v1.7.1
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
)

39
go.sum Normal file
View file

@ -0,0 +1,39 @@
github.com/chasefleming/elem-go v0.17.0 h1:9hqg6OSacy2YgHLpY/soZqXLOOMfhmKG5qukrSgjxlI=
github.com/chasefleming/elem-go v0.17.0/go.mod h1:hz73qILBIKnTgOujnSMtEj20/epI+f6vg71RUilJAA4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

8
views/footer.templ.go Normal file
View file

@ -0,0 +1,8 @@
package views
import "github.com/chasefleming/elem-go"
func RenderFooter() elem.Node {
footerContent := elem.Footer(nil)
return footerContent
}

21
views/head.templ.go Normal file
View file

@ -0,0 +1,21 @@
package views
import (
"github.com/chasefleming/elem-go"
"github.com/chasefleming/elem-go/attrs"
)
func RenderHead() elem.Node {
headContent := elem.Head(nil,
elem.Meta(attrs.Props{attrs.Charset: "UTF-8", attrs.Name: "viewport", attrs.Content: "width=device-width, initial-scale=1.0"}),
elem.Meta(attrs.Props{attrs.HTTPequiv: "X-UA-Compatible", attrs.Content: "IE=edge"}),
elem.Script(attrs.Props{attrs.Src: "https://unpkg.com/htmx.org"}),
elem.Title(nil, elem.Text("The Math Wizard")),
elem.Link(attrs.Props{attrs.Href: "https://cdn.jsdelivr.net/npm/nes.css@2.3.0/css/nes.min.css", attrs.Rel: "stylesheet"}),
elem.Link(attrs.Props{attrs.Rel: "stylesheet", attrs.Href: "https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css"}),
elem.Link(attrs.Props{attrs.Rel: "stylesheet", attrs.Href: "/static/styles/style.css"}),
elem.Script(attrs.Props{attrs.Src: "https://unpkg.com/scrollreveal@4.0.0/dist/scrollreveal.min.js"}),
elem.Style(nil, elem.Text("@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P');")),
)
return headContent
}

24
views/index.templ.go Normal file
View file

@ -0,0 +1,24 @@
package views
import (
"github.com/chasefleming/elem-go"
"github.com/chasefleming/elem-go/attrs"
)
func RenderIndex() string {
headContent := RenderHead()
navContent := RenderNav()
mainContent := RenderMain()
footerContent := RenderFooter()
bodyContent := elem.Body(attrs.Props{attrs.Class: "is-boxed"},
elem.Div(attrs.Props{attrs.Class: "body-wrap boxed-container"},
navContent,
mainContent,
footerContent,
),
)
htmlContent := elem.Html(nil, headContent, bodyContent)
return htmlContent.Render()
}

94
views/main.templ.go Normal file
View file

@ -0,0 +1,94 @@
package views
import (
"github.com/chasefleming/elem-go"
"github.com/chasefleming/elem-go/attrs"
)
func RenderMain() elem.Node {
mainContent := elem.Main(nil,
elem.Section(attrs.Props{attrs.Class: "hero"},
elem.Div(attrs.Props{attrs.Class: "container"},
elem.Div(attrs.Props{attrs.Class: "hero-inner"},
elem.Div(attrs.Props{attrs.Class: "hero-copy"},
elem.H1(attrs.Props{attrs.Class: "hero-title"}, elem.Text("Automatisiere spielerisch das 1x1")),
elem.P(attrs.Props{attrs.Class: "hero-paragraph"}, elem.Text("Zeige was du kannst und kämpfe dich durch den Dungeon.")),
elem.Div(attrs.Props{attrs.Class: "hero-cta"},
elem.A(attrs.Props{attrs.Class: "nes-btn is-primary", attrs.Href: "/login"}, elem.Text("Starte Jetzt!")),
),
),
),
),
),
elem.Section(attrs.Props{attrs.Class: "features section"},
elem.Div(attrs.Props{attrs.Class: "container"},
elem.Div(attrs.Props{attrs.Class: "features-inner section-inner has-bottom-divider"},
elem.Div(attrs.Props{attrs.Class: "features-header text-center"},
elem.Div(attrs.Props{attrs.Class: "container-sm"},
elem.H2(attrs.Props{attrs.Class: "section-title"}, elem.Text("The Math Wizard")),
elem.P(attrs.Props{attrs.Class: "section-paragraph"}, elem.Text("Ohne es zu bemerken lernst du das 1x1.")),
),
),
elem.Div(attrs.Props{attrs.Class: "features-wrap"},
elem.Div(attrs.Props{attrs.Class: "feature is-revealing"},
elem.Div(attrs.Props{attrs.Class: "feature-inner"},
elem.Div(attrs.Props{attrs.Class: "feature-icon"},
elem.Img(attrs.Props{attrs.Src: "/static/images/weapon_sword_ruby.png", attrs.Alt: "Feature 01"})),
elem.Div(attrs.Props{attrs.Class: "feature-content"},
elem.H3(attrs.Props{attrs.Class: "feature-title"}, elem.Text("Immer neue Level")),
elem.P(attrs.Props{attrs.Class: "text-sm mb-0"}, elem.Text("Spannende, zufallsgeneriete Level im Dungeon-Crawler-Stil.")),
),
),
),
elem.Div(attrs.Props{attrs.Class: "feature is-revealing"},
elem.Div(attrs.Props{attrs.Class: "feature-inner"},
elem.Div(attrs.Props{attrs.Class: "feature-icon"},
elem.Img(attrs.Props{attrs.Src: "/static/images/monster_imp.png", attrs.Alt: "Feature 02"})),
elem.Div(attrs.Props{attrs.Class: "feature-content"},
elem.H3(attrs.Props{attrs.Class: "feature-title"}, elem.Text("Angepasste Schwierigkeit")),
elem.P(attrs.Props{attrs.Class: "text-sm mb-0"}, elem.Text("Die Aufgaben passen sich deinen aktuellen Fähigkeiten an.")),
),
),
),
elem.Div(attrs.Props{attrs.Class: "feature is-revealing"},
elem.Div(attrs.Props{attrs.Class: "feature-inner"},
elem.Div(attrs.Props{attrs.Class: "feature-icon"},
elem.Img(attrs.Props{attrs.Src: "/static/images/npc_knight_green.png", attrs.Alt: "Feature 03"})),
elem.Div(attrs.Props{attrs.Class: "feature-content"},
elem.H3(attrs.Props{attrs.Class: "feature-title"}, elem.Text("Fokus auf das was zählt")),
elem.P(attrs.Props{attrs.Class: "text-sm mb-0"}, elem.Text("Fokus auf das Automatisieren der Multiplikation im Zahlenraum von 1 bis 10.")),
),
),
),
elem.Div(attrs.Props{attrs.Class: "feature is-revealing"},
elem.Div(attrs.Props{attrs.Class: "feature-inner"},
elem.Div(attrs.Props{attrs.Class: "feature-icon"},
elem.Img(attrs.Props{attrs.Src: "/static/images/monster_necromancer.png", attrs.Alt: "Feature 04"})),
elem.Div(attrs.Props{attrs.Class: "feature-content"},
elem.H3(attrs.Props{attrs.Class: "feature-title"}, elem.Text("Lernfortschritt immer im Blick")),
elem.P(attrs.Props{attrs.Class: "text-sm mb-0"}, elem.Text("Lernziel und Fortschrittskontrolle, um deinen Fortschritt zu verfolgen.")),
),
),
),
),
),
),
),
elem.Section(attrs.Props{attrs.Class: "cta section"},
elem.Div(attrs.Props{attrs.Class: "container-sm"},
elem.Div(attrs.Props{attrs.Class: "cta-inner section-inner"},
elem.Div(attrs.Props{attrs.Class: "cta-header text-center"},
elem.H2(attrs.Props{attrs.Class: "section-title"}, elem.Text("Bereit los zu legen?")),
elem.P(attrs.Props{attrs.Class: "section-paragraph"}, elem.Text("Fange noch heute an zu lernen.")),
elem.Div(attrs.Props{attrs.Class: "cta-cta"},
elem.A(attrs.Props{attrs.Class: "nes-btn is-primary", attrs.Href: "/login"},
elem.Text("Los geht's")),
),
),
),
),
),
)
return mainContent
}

19
views/nav.templ.go Normal file
View file

@ -0,0 +1,19 @@
package views
import (
"github.com/chasefleming/elem-go"
"github.com/chasefleming/elem-go/attrs"
)
func RenderNav() elem.Node {
navContent := elem.Header(attrs.Props{attrs.Class: "site-header"},
elem.Div(attrs.Props{attrs.Class: "container"},
elem.Nav(nil,
elem.Div(attrs.Props{attrs.Class: "site-header-inner"},
elem.A(attrs.Props{attrs.Class: "", attrs.Href: "/"}),
),
),
),
)
return navContent
}

46
views/profile.templ.go Normal file
View file

@ -0,0 +1,46 @@
package views
import (
"fmt"
"github.com/chasefleming/elem-go"
"github.com/chasefleming/elem-go/attrs"
)
type Stats struct {
Username string
}
func RenderProfile() elem.Node {
stats := Stats{
Username: "TestUser",
}
page := elem.Div(attrs.Props{attrs.Class: "profile"},
elem.Div(attrs.Props{attrs.Style: "padding: 20px; border-radius: 5px; box-shadow: 0 0 4px #433e4c; margin-top: 20px; margin-bottom: 20px; background-color: #2e1e26;"},
elem.Div(attrs.Props{attrs.Class: "container3"},
elem.Section(attrs.Props{attrs.Class: "message-list"},
elem.Section(attrs.Props{attrs.Class: "message -left"},
elem.I(attrs.Props{attrs.Class: "nes-bcrikko"}),
elem.Div(attrs.Props{attrs.Class: "nes-balloon from-left"},
elem.P(nil, elem.Text(fmt.Sprintf("Hallo %s zurück. Wie kann ich dir helfen?", stats.Username))),
),
),
),
elem.Div(attrs.Props{attrs.Class: "container3"},
elem.Div(attrs.Props{attrs.Class: "text-center"},
elem.H3(nil, elem.Text("Was möchtest du tun?"))),
),
elem.Div(nil,
elem.A(attrs.Props{attrs.Class: "nes-btn", attrs.Href: "/learn"}, elem.Text("Lernen")),
elem.A(attrs.Props{attrs.Class: "nes-btn", attrs.Href: "/play"}, elem.Text("Spielen")),
),
),
elem.Div(attrs.Props{attrs.Class: "container3"},
elem.Span(attrs.Props{attrs.Class: "title"}, elem.Text("Dein Fortschritt")),
elem.Span(nil, elem.Text("Du bist aktuell Leven: ")),
),
),
)
return page
}