Compare commits
3 commits
master
...
dev/rebuil
| Author | SHA1 | Date | |
|---|---|---|---|
| 70ddd5e706 | |||
| e0dca3bbd8 | |||
| c2913318de |
6
.env
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
DB_Host: 'db'
|
||||
DB_Port: 3306
|
||||
DB_Name: 'MatheApp'
|
||||
DB_Charset: 'utf8mb4'
|
||||
DB_User: 'MatheApp'
|
||||
DB_Password: 'password'
|
||||
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
|||
# vscode config files
|
||||
./.vscode/*
|
||||
math_wizard_db_data/*
|
||||
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 210 B After Width: | Height: | Size: 210 B |
|
Before Width: | Height: | Size: 249 B After Width: | Height: | Size: 249 B |
|
Before Width: | Height: | Size: 328 B After Width: | Height: | Size: 328 B |
|
Before Width: | Height: | Size: 234 B After Width: | Height: | Size: 234 B |
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 232 B |
|
Before Width: | Height: | Size: 246 B After Width: | Height: | Size: 246 B |
|
|
@ -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,7 +186,7 @@ p {
|
|||
text-align: center;
|
||||
padding-top: 48px;
|
||||
padding-bottom: 88px;
|
||||
background-image: url("./../../images/HeroBanner.png");
|
||||
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,
|
||||
148
db.go
Normal file
|
|
@ -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[:])
|
||||
}
|
||||
24
go.mod
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
module the_math_wizard
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
41
go.sum
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
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/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=
|
||||
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=
|
||||
78
server.go
Normal file
|
|
@ -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
|
||||
}
|
||||
8
views/footer.templ.go
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||