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,5 +1,5 @@
|
|||
# Verwende ein Basisimage mit PHP
|
||||
FROM php:8.4-cli
|
||||
FROM php:8.2-cli
|
||||
|
||||
# Setze das Arbeitsverzeichnis
|
||||
WORKDIR /app
|
||||
|
|
|
|||
33
README.md
|
|
@ -1,33 +0,0 @@
|
|||
# The Math Wizard
|
||||
|
||||
The Math Wizard is a browser game that helps children automate the 1x1. It acts as a dungeon crawler game in which children can fight monsters by solving multiplication problems.
|
||||
|
||||
## Requirements
|
||||
|
||||
To run the game, you need Docker and Docker-Compose. Make sure that these are installed and configured on your system.
|
||||
|
||||
## Installation
|
||||
|
||||
1. clone the repository with `git clone https://github.com/PatrykHegenberg/TheMathWizard.git`.
|
||||
2. change to the project directory with `cd TheMathWizard`.
|
||||
3. run `docker-compose up --build` to install and start the game.
|
||||
|
||||
Once the installation is complete, you can access the game via your web browser under `localhost:8080` or the computer name.
|
||||
|
||||
## Usage
|
||||
|
||||
To create a new account, go to `localhost:8080/signup` in the browser. After signing up, you can start the game and begin fighting monsters and solving multiplication tasks. Your progress will be saved persistently on the hard disk of the host computer.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT license. For more information, see the LICENSE file.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
This project uses the following resources:
|
||||
- [ROT.js](https://github.com/ondras/rot.js/) (BSD-Lizenz)
|
||||
- [Micro Rogue tileset](https://kenney.nl/assets/micro-roguelike) von Kenney.nl (CC0 1.0 Universelle Lizenz)
|
||||
- [NES.css](https://nostalgic-css.github.io/NES.css/) (MIT Lizenz)
|
||||
- [sfxr.me](https://www.sfxr.me/) (Öffentliches Domain)
|
||||
- [Pixel Coin Bild](https://opengameart.org/content/pixel-coins) (CC-BY 3.0 Lizenz)
|
||||
- [Roguelike-Browser-Boilerplate](https://github.com/chr15m/roguelike-browser-boilerplate)
|
||||
|
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,
|
||||
background: linear-gradient(to bottom,
|
||||
#3f2a34,
|
||||
#3f2a34
|
||||
);
|
||||
#3f2a34);
|
||||
}
|
||||
|
||||
.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[:])
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ services:
|
|||
web:
|
||||
#image: math_wizard
|
||||
build:
|
||||
context: /home/pata/dev/WebProg/TheMathWizard
|
||||
dockerfile: Dockerfile.math_wizard
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
|
@ -14,6 +15,7 @@ services:
|
|||
|
||||
db:
|
||||
build:
|
||||
context: /home/pata/dev/WebProg/TheMathWizard
|
||||
dockerfile: Dockerfile.db
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: password
|
||||
|
|
|
|||
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=
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||
}
|
||||
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
|
||||
}
|
||||