From c2913318dec546938cab0dd2dd9959f7473265aa Mon Sep 17 00:00:00 2001 From: Patryk Hegenberg Date: Fri, 29 Dec 2023 09:38:09 +0100 Subject: [PATCH 1/3] wrote the server.go file and introduced another file structure --- .env | 6 + {images => assets/images}/01coin.gif | Bin .../images}/16x16DungeonTileset.png | Bin {images => assets/images}/HeroBanner.png | Bin {images => assets/images}/icon.png | Bin {images => assets/images}/monster_imp.png | Bin .../images}/monster_necromancer.png | Bin .../images}/npc_knight_green.png | Bin .../images}/weapon_silver_topaz.png | Bin .../images}/weapon_sword_red.png | Bin .../images}/weapon_sword_ruby.png | Bin {styles => assets/styles}/game.css | 0 {styles => assets/styles}/mathe.css | 0 {styles => assets/styles}/style.css | 132 ++++++++++++++---- server.go | 78 +++++++++++ 15 files changed, 185 insertions(+), 31 deletions(-) create mode 100644 .env rename {images => assets/images}/01coin.gif (100%) rename {images => assets/images}/16x16DungeonTileset.png (100%) rename {images => assets/images}/HeroBanner.png (100%) rename {images => assets/images}/icon.png (100%) rename {images => assets/images}/monster_imp.png (100%) rename {images => assets/images}/monster_necromancer.png (100%) rename {images => assets/images}/npc_knight_green.png (100%) rename {images => assets/images}/weapon_silver_topaz.png (100%) rename {images => assets/images}/weapon_sword_red.png (100%) rename {images => assets/images}/weapon_sword_ruby.png (100%) rename {styles => assets/styles}/game.css (100%) rename {styles => assets/styles}/mathe.css (100%) rename {styles => assets/styles}/style.css (94%) create mode 100644 server.go diff --git a/.env b/.env new file mode 100644 index 0000000..e29f14f --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +DB_Host: 'db' +DB_Port: 3306 +DB_Name: 'MatheApp' +DB_Charset: 'utf8mb4' +DB_User: 'MatheApp' +DB_Password: 'password' diff --git a/images/01coin.gif b/assets/images/01coin.gif similarity index 100% rename from images/01coin.gif rename to assets/images/01coin.gif diff --git a/images/16x16DungeonTileset.png b/assets/images/16x16DungeonTileset.png similarity index 100% rename from images/16x16DungeonTileset.png rename to assets/images/16x16DungeonTileset.png diff --git a/images/HeroBanner.png b/assets/images/HeroBanner.png similarity index 100% rename from images/HeroBanner.png rename to assets/images/HeroBanner.png diff --git a/images/icon.png b/assets/images/icon.png similarity index 100% rename from images/icon.png rename to assets/images/icon.png diff --git a/images/monster_imp.png b/assets/images/monster_imp.png similarity index 100% rename from images/monster_imp.png rename to assets/images/monster_imp.png diff --git a/images/monster_necromancer.png b/assets/images/monster_necromancer.png similarity index 100% rename from images/monster_necromancer.png rename to assets/images/monster_necromancer.png diff --git a/images/npc_knight_green.png b/assets/images/npc_knight_green.png similarity index 100% rename from images/npc_knight_green.png rename to assets/images/npc_knight_green.png diff --git a/images/weapon_silver_topaz.png b/assets/images/weapon_silver_topaz.png similarity index 100% rename from images/weapon_silver_topaz.png rename to assets/images/weapon_silver_topaz.png diff --git a/images/weapon_sword_red.png b/assets/images/weapon_sword_red.png similarity index 100% rename from images/weapon_sword_red.png rename to assets/images/weapon_sword_red.png diff --git a/images/weapon_sword_ruby.png b/assets/images/weapon_sword_ruby.png similarity index 100% rename from images/weapon_sword_ruby.png rename to assets/images/weapon_sword_ruby.png diff --git a/styles/game.css b/assets/styles/game.css similarity index 100% rename from styles/game.css rename to assets/styles/game.css diff --git a/styles/mathe.css b/assets/styles/mathe.css similarity index 100% rename from styles/mathe.css rename to assets/styles/mathe.css diff --git a/styles/style.css b/assets/styles/style.css similarity index 94% rename from styles/style.css rename to assets/styles/style.css index fbbc2a4..6fbaae5 100644 --- a/styles/style.css +++ b/assets/styles/style.css @@ -20,6 +20,7 @@ h1 { font-size: 2em; margin: 0.67em 0; } + figcaption, figure, main { @@ -50,7 +51,8 @@ img { max-width: 100%; vertical-align: middle; } -.site-header img{ + +.site-header img { height: 60px; } @@ -84,6 +86,7 @@ h6, color: #2e1e26; font-weight: 600; } + h1, .h1 { font-size: 38px; @@ -119,6 +122,7 @@ h2, margin-top: 48px; margin-bottom: 16px; } + h3, .h3 { margin-top: 36px; @@ -141,9 +145,11 @@ p { .container { max-width: 1128px; } + .container-sm { max-width: 848px; } + .container .container-sm { max-width: 800px; padding-left: 0; @@ -167,6 +173,7 @@ p { padding: 12px 0; z-index: 2; } + .site-header-inner { position: relative; display: flex; @@ -179,8 +186,8 @@ p { text-align: center; padding-top: 48px; padding-bottom: 88px; - background-image: url("./../../images/HeroBanner.png"); - background-repeat: no-repeat; + background-image: url("/static/images/HeroBanner.png"); + background-repeat: no-repeat; background-position: center; background-size: cover; } @@ -192,15 +199,18 @@ footer img { .hero-copy { position: relative; } + .hero-paragraph { margin-bottom: 32px; } + .hero-cta { max-width: 400px; margin-left: auto; margin-right: auto; margin-bottom: 80px; } + .lights-toggle { color: rgba(107, 122, 144, 0.64); } @@ -211,10 +221,12 @@ footer img { padding-top: 88px; padding-bottom: 120px; } + .hero-inner { display: flex; justify-content: space-between; } + .hero-copy { padding-top: 40px; padding-right: 48px; @@ -222,33 +234,42 @@ footer img { max-width: 512px; z-index: 1; } + .hero-title { margin-bottom: 16px; } + .hero-paragraph { margin-bottom: 32px; } + .hero-cta { display: flex; align-items: center; margin: 0; } + .hero-cta .button { min-width: 170px; } + .hero-cta .button:first-child { margin-right: 32px; } + .header-illustration { display: block; } + .hero-media { z-index: 0; } + .hero-media img, .hero-media svg { max-width: none; } + .header-illustration-image { display: block; position: absolute; @@ -258,6 +279,7 @@ footer img { height: 324px; } } + .features-wrap { max-width: 540px; margin: 0 auto; @@ -267,21 +289,26 @@ footer img { text-align: center; margin-bottom: 48px; } + .feature:last-of-type { margin-bottom: 0; } + .feature-icon { display: inline-flex; margin-bottom: 16px; } + .feature-icon img, .feature-icon svg { max-width: none; } + .feature-title { position: relative; margin-bottom: 26px; } + .feature-title::after { content: ''; width: 32px; @@ -291,20 +318,25 @@ footer img { left: calc(50% - 16px); background: #e9edf3; } - .feature-title::after { + +.feature-title::after { background: #3f2a34; } + @media (min-width: 641px) { .features { position: relative; } + .features .section-inner { padding-bottom: 100px; } + .features .section-paragraph { padding-left: 72px; padding-right: 72px; } + .features::before { content: ''; width: 100%; @@ -312,40 +344,47 @@ footer img { position: absolute; left: 0; top: 168px; - background: linear-gradient( - to bottom, - #3f2a34, - #3f2a34 - ); + background: linear-gradient(to bottom, + #3f2a34, + #3f2a34); } - .features::before { + + .features::before { display: none; } + .feature { text-align: left; } + .feature-inner { display: flex; } + .feature-icon { display: block; margin-top: 8px; margin-right: 32px; margin-bottom: 0; } + .feature-title::after { left: 0; } } + .cta { position: relative; } + .cta .section-inner { padding-bottom: 64px; } + .cta .section-paragraph { margin-bottom: 32px; } + .cta::before { content: ''; position: absolute; @@ -354,32 +393,39 @@ footer img { height: 263px; width: 1440px; } + .cta-cta { max-width: 400px; margin-left: auto; margin-right: auto; } + @media (max-width: 639px) { .cta-cta .button { display: flex; } } + @media (min-width: 641px) { .cta .section-inner { padding-bottom: 128px; } + .cta .section-paragraph { margin-bottom: 40px; padding-left: 72px; padding-right: 72px; } + .cta::before { bottom: 0; } } + .is-boxed { background: #e9edf3; } + .body-wrap { background: #fff; overflow: hidden; @@ -387,14 +433,17 @@ footer img { flex-direction: column; min-height: 100vh; } + .boxed-container { max-width: 1440px; margin: 0 auto; box-shadow: 0 16px 48px rgba(255, 255, 255, 0.5); } + main { flex: 1 0 auto; } + .section-inner { position: relative; padding-top: 48px; @@ -432,6 +481,7 @@ main { margin: 0 auto; height: 500px; } + .container2 { display: flex; justify-content: center; @@ -440,6 +490,7 @@ main { margin: 0 auto; height: 750px; } + .container3 { display: flex; justify-content: center; @@ -450,6 +501,7 @@ main { background-color: #2e1e26; padding: 20px; } + .container4 { display: flex; justify-content: center; @@ -460,6 +512,7 @@ main { background-color: #2e1e26; padding: 20px; } + .footer-copyright { flex: none; width: 80%; @@ -467,6 +520,7 @@ main { justify-content: center; align-items: center; } + .register { height: 1200px; display: flex; @@ -490,6 +544,7 @@ main { .nes-field { max-width: 400px; } + .login { height: 1200px; display: flex; @@ -510,6 +565,7 @@ main { border-radius: 5px; box-shadow: #3f2a34; } + .learnContainer { display: flex; justify-content: center; @@ -523,6 +579,7 @@ main { border-radius: 5px; box-shadow: 0 0 4px #433e4c } + .profile { height: 1200px; display: flex; @@ -532,58 +589,68 @@ main { border-radius: 5px; box-shadow: #3f2a34; } + .container .login { display: flex; justify-content: center; align-items: center; text-align: center; } + .asset-dark { visibility: hidden; opacity: 0; } + .is-loaded .asset-dark { visibility: visible; opacity: 1; } + .asset-dark { display: none; } - .asset-dark { +.asset-dark { display: block; } - a { + +a { color: #8595ae; } - h1, - h2, - h3, - h4, - h5, - h6, - .h1, - .h2, - .h3, - .h4, - .h5, - .h6 { + +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { color: #fff !important; } + .is-boxed { background: #3f2a34; } - .body-wrap { + +.body-wrap { background: #2e1e26; } - .boxed-container { + +.boxed-container { box-shadow: 0 16px 48px #433e4c; } - .has-top-divider { +.has-top-divider { position: relative; } - .has-top-divider::before { + +.has-top-divider::before { content: ''; position: absolute; top: 0; @@ -593,10 +660,12 @@ main { height: 1px; background: #3f2a34; } - .has-bottom-divider { + +.has-bottom-divider { position: relative; } - .has-bottom-divider::after { + +.has-bottom-divider::after { content: ''; position: absolute; bottom: 0; @@ -606,6 +675,7 @@ main { height: 1px; background: #3f2a34; } + body, a, h1, @@ -625,4 +695,4 @@ h6, .boxed-container { transition: box-shadow 0.15s ease; -} +} \ No newline at end of file diff --git a/server.go b/server.go new file mode 100644 index 0000000..3ad5e8e --- /dev/null +++ b/server.go @@ -0,0 +1,78 @@ +package main + +import ( + // "fmt" + "net/http" + // "os" + // "os/exec" + // "runtime" + // "strconv" + + // "github.com/chasefleming/elem-go" + // "github.com/chasefleming/elem-go/attrs" + // "github.com/chasefleming/elem-go/htmx" + "the_math_wizard/views" + + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" +) + +func main() { + e := echo.New() + e.Static("/static", "assets") + + // Middleware + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + // Routes + e.GET("/", RenderIndexRoute) + e.GET("/learn", RenderLearnRoute) + e.GET("/mathe", RenderMatheRoute) + e.GET("/game", RenderGameRoute) + e.GET("/login", RenderLoginRoute) + e.GET("/register", RenderRegisterRoute) + e.GET("/profile", RenderProfileRoute) + e.GET("/logout", LogoutRoute) + e.GET("/delete", DeleteRoute) + e.GET("/updateData", RenderUpdateRoute) + e.GET("/deleteUser", RenderDeleteRoute) + + // Start the server + e.Logger.Fatal(e.Start(":3000")) + +} + +func RenderIndexRoute(c echo.Context) error { + return c.HTML(http.StatusOK, views.RenderIndex()) +} +func RenderLearnRoute(c echo.Context) error { + return nil +} +func RenderMatheRoute(c echo.Context) error { + return nil +} +func RenderGameRoute(c echo.Context) error { + return nil +} +func RenderLoginRoute(c echo.Context) error { + return nil +} +func RenderRegisterRoute(c echo.Context) error { + return nil +} +func RenderProfileRoute(c echo.Context) error { + return nil +} +func LogoutRoute(c echo.Context) error { + return nil +} +func DeleteRoute(c echo.Context) error { + return nil +} +func RenderUpdateRoute(c echo.Context) error { + return nil +} +func RenderDeleteRoute(c echo.Context) error { + return nil +} From e0dca3bbd86f17bcbecf41a9dfa1e6ba0345c443 Mon Sep 17 00:00:00 2001 From: Patryk Hegenberg Date: Fri, 29 Dec 2023 12:26:16 +0100 Subject: [PATCH 2/3] add db.go for database connection --- .gitignore | 1 + db/db.go | 138 +++++++++++++++++++++++++++++++++++++++++ go.mod | 23 +++++++ go.sum | 39 ++++++++++++ views/footer.templ.go | 8 +++ views/head.templ.go | 21 +++++++ views/index.templ.go | 24 +++++++ views/main.templ.go | 94 ++++++++++++++++++++++++++++ views/nav.templ.go | 19 ++++++ views/profile.templ.go | 46 ++++++++++++++ 10 files changed, 413 insertions(+) create mode 100644 db/db.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 views/footer.templ.go create mode 100644 views/head.templ.go create mode 100644 views/index.templ.go create mode 100644 views/main.templ.go create mode 100644 views/nav.templ.go create mode 100644 views/profile.templ.go diff --git a/.gitignore b/.gitignore index d40cde8..09edf85 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ # vscode config files ./.vscode/* +math_wizard_db_data/* \ No newline at end of file diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..9676df8 --- /dev/null +++ b/db/db.go @@ -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 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7c4e19e --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..596e254 --- /dev/null +++ b/go.sum @@ -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= diff --git a/views/footer.templ.go b/views/footer.templ.go new file mode 100644 index 0000000..7e5a9c5 --- /dev/null +++ b/views/footer.templ.go @@ -0,0 +1,8 @@ +package views + +import "github.com/chasefleming/elem-go" + +func RenderFooter() elem.Node { + footerContent := elem.Footer(nil) + return footerContent +} diff --git a/views/head.templ.go b/views/head.templ.go new file mode 100644 index 0000000..c634d17 --- /dev/null +++ b/views/head.templ.go @@ -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 +} diff --git a/views/index.templ.go b/views/index.templ.go new file mode 100644 index 0000000..7ac46c0 --- /dev/null +++ b/views/index.templ.go @@ -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() +} diff --git a/views/main.templ.go b/views/main.templ.go new file mode 100644 index 0000000..87e30f6 --- /dev/null +++ b/views/main.templ.go @@ -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 +} diff --git a/views/nav.templ.go b/views/nav.templ.go new file mode 100644 index 0000000..7a7d0de --- /dev/null +++ b/views/nav.templ.go @@ -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 +} diff --git a/views/profile.templ.go b/views/profile.templ.go new file mode 100644 index 0000000..d7a581c --- /dev/null +++ b/views/profile.templ.go @@ -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 +} From 70ddd5e706ae357dd3a0f9343181b887e0a9af3e Mon Sep 17 00:00:00 2001 From: Patryk Hegenberg Date: Fri, 29 Dec 2023 14:47:22 +0100 Subject: [PATCH 3/3] added more changes to db.go --- {scripts => assets/scripts}/addition.js | 0 {scripts => assets/scripts}/game.js | 0 {scripts => assets/scripts}/mathe.js | 0 db.go | 148 ++++++++++++++++++++++++ db/db.go | 138 ---------------------- go.mod | 1 + go.sum | 2 + 7 files changed, 151 insertions(+), 138 deletions(-) rename {scripts => assets/scripts}/addition.js (100%) rename {scripts => assets/scripts}/game.js (100%) rename {scripts => assets/scripts}/mathe.js (100%) create mode 100644 db.go delete mode 100644 db/db.go diff --git a/scripts/addition.js b/assets/scripts/addition.js similarity index 100% rename from scripts/addition.js rename to assets/scripts/addition.js diff --git a/scripts/game.js b/assets/scripts/game.js similarity index 100% rename from scripts/game.js rename to assets/scripts/game.js diff --git a/scripts/mathe.js b/assets/scripts/mathe.js similarity index 100% rename from scripts/mathe.js rename to assets/scripts/mathe.js diff --git a/db.go b/db.go new file mode 100644 index 0000000..a614f60 --- /dev/null +++ b/db.go @@ -0,0 +1,148 @@ +package main + +import ( + "crypto/sha256" + "database/sql" + "encoding/hex" + "fmt" + "os" + + _ "github.com/go-sql-driver/mysql" + "github.com/joho/godotenv" + "github.com/labstack/echo/v4" +) + +var db *sql.DB + +func init() { + err := godotenv.Load() + if err != nil { + fmt.Println("Error loading .env file") + os.Exit(1) + } + + dbHost := os.Getenv("DB_Host") + dbPort := os.Getenv("DB_Port") + dbName := os.Getenv("DB_Name") + dbCharset := os.Getenv("DB_Charset") + dbUser := os.Getenv("DB_User") + dbPassword := os.Getenv("DB_Password") + + // Connect to the database + db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s", dbUser, dbPassword, dbHost, dbPort, dbName, dbCharset)) + // db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(db:3306)/%s?charset=utf8mb4", dbUser, dbPassword, dbName)) + + if err != nil { + fmt.Println("Error connecting to the database") + os.Exit(1) + } +} + +// User struct +type User struct { + ID int `json:"id"` + Username string `json:"username"` + Vorname string `json:"vorname"` + Nachname string `json:"nachname"` + Email string `json:"email"` + Password string `json:"password"` + LessonCount int `json:"lesson_count"` + Level int `json:"level"` + XP int `json:"xp"` + Coins int `json:"coins"` + IsAdmin sql.NullBool `json:"is_admin"` +} + +// CRUD operations + +func getUsers(c echo.Context) error { + rows, err := db.Query("SELECT * FROM user") + if err != nil { + return err + } + defer rows.Close() + + users := []User{} + for rows.Next() { + user := User{} + err := rows.Scan(&user.ID, &user.Username, &user.Vorname, &user.Nachname, &user.Email, &user.Password, &user.LessonCount, &user.Level, &user.XP, &user.Coins, &user.IsAdmin) + if err != nil { + return err + } + users = append(users, user) + } + + return c.JSON(200, users) +} + +func getUser(c echo.Context) error { + id := c.Param("id") + + row := db.QueryRow("SELECT * FROM user WHERE id = ?", id) + + user := User{} + err := row.Scan(&user.ID, &user.Username, &user.Vorname, &user.Nachname, &user.Email, &user.Password, &user.LessonCount, &user.Level, &user.XP, &user.Coins, &user.IsAdmin) + if err != nil { + return err + } + + return c.JSON(200, user) +} + +func createUser(c echo.Context) error { + user := new(User) + if err := c.Bind(user); err != nil { + return err + } + + // Hash das Passwort + user.Password = hashPassword(user.Password) + + result, err := db.Exec("INSERT INTO user (username, vorname, nachname, email, password, lesson_count, level, xp, coins, isAdmin) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + user.Username, user.Vorname, user.Nachname, user.Email, user.Password, user.LessonCount, user.Level, user.XP, user.Coins, user.IsAdmin) + if err != nil { + return err + } + + lastInsertID, err := result.LastInsertId() + if err != nil { + return err + } + + user.ID = int(lastInsertID) + + return c.JSON(201, user) +} + +func updateUser(c echo.Context) error { + id := c.Param("id") + + user := new(User) + if err := c.Bind(user); err != nil { + return err + } + + _, err := db.Exec("UPDATE user SET username = ?, vorname = ?, nachname = ?, email = ?, password = ?, lesson_count = ?, level = ?, xp = ?, coins = ?, isAdmin = ? WHERE id = ?", + user.Username, user.Vorname, user.Nachname, user.Email, user.Password, user.LessonCount, user.Level, user.XP, user.Coins, user.IsAdmin, id) + if err != nil { + return err + } + + return c.NoContent(204) +} + +func deleteUser(c echo.Context) error { + id := c.Param("id") + + _, err := db.Exec("DELETE FROM user WHERE id = ?", id) + if err != nil { + return err + } + + return c.NoContent(204) +} + +func hashPassword(password string) string { + hash := sha256.Sum256([]byte(password)) + return hex.EncodeToString(hash[:]) +} diff --git a/db/db.go b/db/db.go deleted file mode 100644 index 9676df8..0000000 --- a/db/db.go +++ /dev/null @@ -1,138 +0,0 @@ -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 -} diff --git a/go.mod b/go.mod index 7c4e19e..0212516 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21.5 require ( github.com/chasefleming/elem-go v0.17.0 + github.com/joho/godotenv v1.5.1 github.com/labstack/echo/v4 v4.11.4 ) diff --git a/go.sum b/go.sum index 596e254..213e730 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt 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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 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=