summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--components/feedback.html33
-rw-r--r--components/index.html70
-rw-r--r--components/question.html75
-rw-r--r--config.yml9
-rw-r--r--demoon.dbbin1544192 -> 1564672 bytes
-rw-r--r--internal/database/repos/main.go3
-rw-r--r--internal/database/repos/questions.go39
-rw-r--r--internal/handlers/main.go94
-rw-r--r--internal/models/models.go6
-rw-r--r--internal/server/router.go2
11 files changed, 208 insertions, 124 deletions
diff --git a/.gitignore b/.gitignore
index ff7a93d..db4fd2c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
secrethitler
-config.yml
demoon
tags
store.json
diff --git a/components/feedback.html b/components/feedback.html
deleted file mode 100644
index 0eda969..0000000
--- a/components/feedback.html
+++ /dev/null
@@ -1,33 +0,0 @@
-{{define "feedback"}}
-{{if .Correct}}
-<div class="correct-feedback">
- <div class="correct-answer">Correct!</div>
- <div class="explanation">{{.Explanation}}</div>
- {{if .ShowNext}}
- <button
- hx-get="/next-question?current_id={{.ID}}"
- hx-target="#ancestor"
- hx-swap="outerHTML"
- class="next-button"
- data-testid="next-button">
- Next Question →
- </button>
- {{end}}
-</div>
-{{else}}
-<div class="error-feedback">
- <div class="wrong-answer">Try Again</div>
- <div class="explanation">{{.Explanation}}</div>
- {{if .ShowNext}}
- <button
- hx-get="/next-question?current_id={{.ID}}"
- hx-target="#ancestor"
- hx-swap="outerHTML"
- class="next-button"
- data-testid="next-button">
- Next Question →
- </button>
- {{end}}
-</div>
-{{end}}
-{{end}}
diff --git a/components/index.html b/components/index.html
index 9c43a50..816fd21 100644
--- a/components/index.html
+++ b/components/index.html
@@ -9,66 +9,22 @@
<script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
</head>
<body>
- <div id="ancestor">
- <div class="question-text" data-testid="question">
- {{.Text}}
- </div>
- <div id="feedback" data-testid="feedback">
- {{template "feedback" .}}
- {{if .ShowNext}}
- <button
- hx-get="/next-question?current_id={{.ID}}"
- hx-target="#ancestor"
- hx-swap="outerHTML"
- class="next-button"
- data-testid="next-button">
- Next Question →
- </button>
- {{end}}
- </div>
- {{template "feedback" .}}
- <div id="options">
- <button
- data-testid="option1"
- hx-post="/answer"
- hx-vals='{"selected": "0", "question_id": "{{.ID}}"}'
- hx-target="#feedback"
- hx-swap="innerHTML"
- class="option-button"
- >
- {{.Option1}}
- </button>
- <button
- data-testid="option2"
- hx-post="/answer"
- hx-vals='{"selected": "1", "question_id": "{{.ID}}"}'
- hx-target="#feedback"
- hx-swap="innerHTML"
- class="option-button"
- >
- {{.Option2}}
- </button>
- <button
- data-testid="option3"
- hx-post="/answer"
- hx-vals='{"selected": "2", "question_id": "{{.ID}}"}'
- hx-target="#feedback"
- hx-swap="innerHTML"
- class="option-button"
- >
- {{.Option3}}
- </button>
+ <div class="container">
+ <h1>Mixed Übungen</h1>
+ <div class="ubung-list">
+ {{range .}}
+ <p>
<button
- data-testid="option4"
- hx-post="/answer"
- hx-vals='{"selected": "3", "question_id": "{{.ID}}"}'
- hx-target="#feedback"
- hx-swap="innerHTML"
- class="option-button"
- >
- {{.Option4}}
+ class="ubung-button"
+ hx-get="/mixed?id={{.ID}}"
+ hx-target="#ancestor"
+ hx-swap="outerHTML">
+ {{.Name}} (Level {{.LevelID}})
</button>
+ </p>
+ {{end}}
</div>
+ <div id="ancestor"></div>
</div>
</body>
</html>
diff --git a/components/question.html b/components/question.html
new file mode 100644
index 0000000..ef4618b
--- /dev/null
+++ b/components/question.html
@@ -0,0 +1,75 @@
+{{define "question"}}
+<div id="question">
+ {{if .Requirement}}
+ <p>{{.Requirement}}</p>
+ {{end}}
+ <div class="question-text" data-testid="question">
+ {{.Text}}
+ </div>
+ <div id="reaction">
+ {{if eq .Status 1}}
+ Correct!
+ {{end}}
+ {{if eq .Status 2}}
+ Wrong.
+ {{end}}
+ </div>
+ <div id="feedback" data-testid="feedback">
+ {{if ne .Status 0}}
+ <div>
+ {{.Explanation}}
+ </div>
+ <button
+ hx-get="/next-question?current_id={{.ID}}"
+ hx-target="#ancestor"
+ hx-swap="outerHTML"
+ class="next-button"
+ data-testid="next-button">
+ Next Question →
+ </button>
+ {{end}}
+ </div>
+ <div id="options">
+ <button
+ data-testid="option1"
+ hx-post="/answer"
+ hx-vals='{"selected": "0", "question_id": "{{.ID}}"}'
+ hx-target="#ancestor"
+ hx-swap="innerHTML"
+ class="option-button"
+ >
+ {{.Option1}}
+ </button>
+ <button
+ data-testid="option2"
+ hx-post="/answer"
+ hx-vals='{"selected": "1", "question_id": "{{.ID}}"}'
+ hx-target="#ancestor"
+ hx-swap="innerHTML"
+ class="option-button"
+ >
+ {{.Option2}}
+ </button>
+ <button
+ data-testid="option3"
+ hx-post="/answer"
+ hx-vals='{"selected": "2", "question_id": "{{.ID}}"}'
+ hx-target="#ancestor"
+ hx-swap="innerHTML"
+ class="option-button"
+ >
+ {{.Option3}}
+ </button>
+ <button
+ data-testid="option4"
+ hx-post="/answer"
+ hx-vals='{"selected": "3", "question_id": "{{.ID}}"}'
+ hx-target="#ancestor"
+ hx-swap="innerHTML"
+ class="option-button"
+ >
+ {{.Option4}}
+ </button>
+ </div>
+</div>
+{{end}}
diff --git a/config.yml b/config.yml
new file mode 100644
index 0000000..c4d245a
--- /dev/null
+++ b/config.yml
@@ -0,0 +1,9 @@
+SERVICE:
+ HOST: 'localhost'
+ PORT: '9000'
+
+BASE_URL: localhost:9000
+
+DBURI: demoon.db
+
+SESSION_LIFETIME_SECONDS: 7200
diff --git a/demoon.db b/demoon.db
index f7b60c7..9a0c635 100644
--- a/demoon.db
+++ b/demoon.db
Binary files differ
diff --git a/internal/database/repos/main.go b/internal/database/repos/main.go
index f6835c7..e49d303 100644
--- a/internal/database/repos/main.go
+++ b/internal/database/repos/main.go
@@ -2,12 +2,11 @@ package repos
import (
"github.com/jmoiron/sqlx"
- "demoon/internal/models"
)
type FullRepo interface {
DefaultsRepo
- DBGetQuestion(id string) (*models.Question, error)
+ QuestionsRepo
}
type Provider struct {
diff --git a/internal/database/repos/questions.go b/internal/database/repos/questions.go
index d9978a4..9d0396e 100644
--- a/internal/database/repos/questions.go
+++ b/internal/database/repos/questions.go
@@ -1,9 +1,46 @@
package repos
-import "demoon/internal/models"
+import (
+ "demoon/internal/models"
+ "fmt"
+)
type QuestionsRepo interface {
DBGetQuestion(id string) (*models.Question, error)
+ DBGetMixedUbung(id uint32) (*models.MixedUbung, error)
+ DBListMixed(limit uint32) ([]models.MixedUbung, error)
+ DBGetQuestionsByMixedID(id string) ([]models.Question, error)
+}
+
+func (p *Provider) DBGetMixedUbung(id uint32) (*models.MixedUbung, error) {
+ var ubung models.MixedUbung
+ err := p.db.Get(&ubung, "SELECT * FROM Table_Mixed WHERE mixedid = ?", id)
+ if err != nil {
+ return nil, err
+ }
+ return &ubung, nil
+}
+
+func (p *Provider) DBListMixed(limit uint32) ([]models.MixedUbung, error) {
+ var ubungs []models.MixedUbung
+ query := "SELECT * FROM Table_Mixed ORDER BY MixedID"
+ if limit > 0 {
+ query = fmt.Sprintf("SELECT * FROM Table_Mixed ORDER BY MixedID LIMIT %d", limit)
+ }
+ err := p.db.Select(&ubungs, query)
+ if err != nil {
+ return nil, err
+ }
+ return ubungs, nil
+}
+
+func (p *Provider) DBGetQuestionsByMixedID(id string) ([]models.Question, error) {
+ var questions []models.Question
+ err := p.db.Select(&questions, "SELECT * FROM questions WHERE mixed_id = ? ORDER BY id", id)
+ if err != nil {
+ return nil, err
+ }
+ return questions, nil
}
func (p *Provider) DBGetQuestion(id string) (*models.Question, error) {
diff --git a/internal/handlers/main.go b/internal/handlers/main.go
index 4a64380..34f37d9 100644
--- a/internal/handlers/main.go
+++ b/internal/handlers/main.go
@@ -13,10 +13,9 @@ import (
// Handlers structure
type Handlers struct {
- cfg config.Config
- log *slog.Logger
- repo repos.FullRepo
- showNext bool // Tracks when to show next button
+ cfg config.Config
+ log *slog.Logger
+ repo repos.FullRepo
}
// NewHandlers constructor
@@ -80,16 +79,23 @@ func (h *Handlers) HandleAnswer(w http.ResponseWriter, r *http.Request) {
return
}
selectedIdx++ // in db index starts from 1
- h.showNext = true
- feedback := ""
+ // Render feedback section with full question state
+ tmpl, err := template.ParseGlob("components/*.html")
+ if err != nil {
+ abortWithError(w, err.Error())
+ return
+ }
+ question.Status = 2
if selectedIdx == int(question.CorrectIndex) {
- feedback = `<div data-testid="feedback" class="feedback">Correct! 🎉</div>`
- } else {
- feedback = `<div data-testid="feedback" class="feedback">Wrong answer! The correct answer was: ` + getCorrectOption(question) + `</div>`
+ question.Status = 1
}
+ // Execute template with question data including status
w.Header().Set("Content-Type", "text/html")
- w.Write([]byte(feedback))
+ err = tmpl.ExecuteTemplate(w, "main", question)
+ if err != nil {
+ h.log.Error("failed to render feedback template", "error", err)
+ }
}
func getCorrectOption(q *models.Question) string {
@@ -107,10 +113,39 @@ func getCorrectOption(q *models.Question) string {
}
}
+func (h *Handlers) HandleMixedUbung(w http.ResponseWriter, r *http.Request) {
+ mixedID := r.URL.Query().Get("id")
+ if mixedID == "" {
+ h.log.Error("missing mixed ID parameter")
+ abortWithError(w, "Missing exercise ID")
+ return
+ }
+
+ questions, err := h.repo.DBGetQuestionsByMixedID(mixedID)
+ if err != nil {
+ h.log.Error("failed to get questions for mixed exercise", "error", err, "mixed_id", mixedID)
+ abortWithError(w, "Failed to load exercise")
+ return
+ }
+
+ if len(questions) == 0 {
+ h.log.Error("no questions found for mixed exercise", "mixed_id", mixedID)
+ abortWithError(w, "Exercise contains no questions")
+ return
+ }
+
+ // Render first question in the sequence
+ h.renderQuestion(w, &questions[0])
+}
+
func (h *Handlers) HandleNextQuestion(w http.ResponseWriter, r *http.Request) {
currentID := r.URL.Query().Get("current_id")
- nextID, _ := strconv.Atoi(currentID)
- nextID++
+ currID, err := strconv.Atoi(currentID)
+ if err != nil {
+ h.log.Error("invalid current question ID", "error", err, "current_id", currentID)
+ currID = 0 // Start from first question if invalid ID
+ }
+ nextID := currID + 1
question, err := h.repo.DBGetQuestion(strconv.Itoa(nextID))
if err != nil {
@@ -119,7 +154,8 @@ func (h *Handlers) HandleNextQuestion(w http.ResponseWriter, r *http.Request) {
return
}
- h.showNext = false // Reset flag for new question
+ h.log.Debug("returning new question", "q", question)
+
h.renderQuestion(w, question)
}
@@ -133,34 +169,32 @@ func (h *Handlers) renderQuestion(w http.ResponseWriter, question *models.Questi
// Add ShowNext flag to template data
type TemplateData struct {
*models.Question
- ShowNext bool
- Correct bool
}
- err = tmpl.ExecuteTemplate(w, "main", &TemplateData{
- Question: question,
- ShowNext: h.showNext,
- })
+ err = tmpl.ExecuteTemplate(w, "question", question)
if err != nil {
h.log.Error("failed to render template", "error", err)
}
- question, err = h.repo.DBGetQuestion("1")
- if err != nil {
- h.log.Error("failed to get question", "error", err)
- abortWithError(w, "Question not found")
- return
- }
}
func (h *Handlers) MainPage(w http.ResponseWriter, r *http.Request) {
- question, err := h.repo.DBGetQuestion("1")
+ ubungs, err := h.repo.DBListMixed(10)
if err != nil {
- h.log.Error("failed to get question", "error", err)
- abortWithError(w, "Question not found")
+ h.log.Error("failed to get mixed ubungs", "error", err)
+ abortWithError(w, "Failed to load exercises")
return
}
- h.showNext = false
- h.renderQuestion(w, question)
+ tmpl, err := template.ParseGlob("components/*.html")
+ if err != nil {
+ abortWithError(w, err.Error())
+ return
+ }
+
+ w.Header().Set("Content-Type", "text/html")
+ err = tmpl.ExecuteTemplate(w, "main", ubungs)
+ if err != nil {
+ h.log.Error("failed to render template", "error", err)
+ }
}
diff --git a/internal/models/models.go b/internal/models/models.go
index 8e30a6c..7cef85e 100644
--- a/internal/models/models.go
+++ b/internal/models/models.go
@@ -15,4 +15,10 @@ type (
ExamID uint32 `db:"exam_id"`
MixedID uint32 `db:"mixed_id"`
}
+ MixedUbung struct {
+ ID uint32 `db:"MixedID"`
+ Name string `db:"MixedName"`
+ Status int `db:"MixedStatus"`
+ LevelID uint32 `db:"LevelID"`
+ }
)
diff --git a/internal/server/router.go b/internal/server/router.go
index 43a29b9..bb73e71 100644
--- a/internal/server/router.go
+++ b/internal/server/router.go
@@ -23,6 +23,8 @@ func (srv *server) ListenToRequests() {
mux.HandleFunc("GET /ping", h.Ping)
mux.HandleFunc("GET /", h.MainPage)
mux.HandleFunc("POST /answer", h.HandleAnswer)
+ mux.HandleFunc("GET /next-question", h.HandleNextQuestion)
+ mux.HandleFunc("GET /mixed", h.HandleMixedUbung)
// mux.HandleFunc("POST /login", h.HandleLogin)
// mux.HandleFunc("POST /signup", h.HandleSignup)