17 Commits

Author SHA1 Message Date
7144489afb Bessere Beispiele 2023-10-17 05:25:18 +02:00
76f1fe9588 Großer Umbruch der Architektur, halb fertig, zu viele Details zum Aufzählen, in Zukunft wieder kleine Commits! 2023-10-16 18:51:52 +02:00
726c8b6dcb Login für eingeladene Prüflinge implementiert 2023-10-10 20:57:53 +02:00
608879d008 Generierte UUID in einen asynchronen Speicher schreiben 2023-10-10 19:50:19 +02:00
616df72041 package db zu data umbenannt, außerdem db.Open() zu data.OpenDB() 2023-10-10 19:19:17 +02:00
1597d38d34 UUID-Button repariert 2023-10-10 19:18:34 +02:00
8dbb5f946d UUID-Element zum Ersetzen des "Einladen"-Buttons erstellt 2023-10-10 19:08:59 +02:00
7c7cb5959d GenerateUUID zum erstellen einer pseudozufälligen, 8 Stellen langen Hexadezimalzahl 2023-10-10 19:06:02 +02:00
2beb90a345 Button zum Generieren einer UUID hinzugefügt 2023-10-10 18:55:54 +02:00
f80dca4b10 Bei 82870e10 war question.html nicht gespeichert 2023-10-10 18:55:34 +02:00
bf05bc0be7 Der Participant muss nicht das ganze Questionaire kennen, nur die UUID 2023-10-10 18:52:29 +02:00
f6a073fc39 Alle nötigen types für Questionaire eingefügt 2023-10-10 18:50:54 +02:00
82870e100f question.html erstellt 2023-10-10 18:43:48 +02:00
2b119f6752 Leerzeichen in SQL-Abfragen ergänzt 2023-10-08 15:16:47 +02:00
c04932383e Reihenfolge der ausgegebenen Ergebnisse umgedreht 2023-10-08 15:13:22 +02:00
8ea0c2964a Spalten in Ausgabetabelle umbenannt 2023-10-08 15:12:52 +02:00
519dc82023 Automatisches Suchen ohne Submit-Button 2023-10-08 11:01:57 +02:00
17 changed files with 560 additions and 117 deletions

44
.air.toml Normal file
View File

@ -0,0 +1,44 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ."
delay = 0
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true

1
.gitignore vendored
View File

@ -1 +0,0 @@
create_tables.sql

96
create_tables.sql Normal file
View File

@ -0,0 +1,96 @@
USE sicherheitsunterweisung;
DROP TABLE IF EXISTS instructors;
DROP TABLE IF EXISTS briefings;
DROP TABLE IF EXISTS participants;
DROP TABLE IF EXISTS questions;
DROP TABLE IF EXISTS given_answers;
CREATE TABLE instructors (
id INT NOT NULL AUTO_INCREMENT,
first_name VARCHAR(32) NOT NULL,
last_name VARCHAR(32) NOT NULL,
personnel_id INT NOT NULL,
PRIMARY KEY(id)
);
CREATE TABLE briefings (
id INT NOT NULL AUTO_INCREMENT,
date DATE NOT NULL,
time TIME NOT NULL,
location VARCHAR(32) NOT NULL,
as_of DATE NOT NULL,
instructor_id INT NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY(instructor_id) REFERENCES instructors(id)
);
CREATE TABLE participants (
id INT NOT NULL AUTO_INCREMENT,
first_name VARCHAR(32) NOT NULL,
last_name VARCHAR(32) NOT NULL,
company VARCHAR(32) NOT NULL,
PRIMARY KEY(id)
);
CREATE TABLE questions (
id INT NOT NULL AUTO_INCREMENT,
question VARCHAR(256) NOT NULL,
answer_1 VARCHAR(64) NOT NULL,
answer_2 VARCHAR(64) NOT NULL,
answer_3 VARCHAR(64) NOT NULL,
answer_4 VARCHAR(64) NOT NULL,
correct_answer INT NOT NULL,
PRIMARY KEY(id)
);
CREATE TABLE given_answers (
briefing_id INT NOT NULL,
participant_id INT NOT NULL,
question_id INT NOT NULL,
given_answer INT NOT NULL,
PRIMARY KEY(briefing_id, participant_id, question_id),
FOREIGN KEY(briefing_id) REFERENCES briefings(id),
FOREIGN KEY(participant_id) REFERENCES participants(id),
FOREIGN KEY(question_id) REFERENCES questions(id)
);
INSERT INTO instructors
(first_name, last_name, personnel_id)
VALUES
( 'Jason', 'Streifling', '123456' ),
( 'Tim', 'Taler', '123457' ),
( 'Georg', 'aus dem Jungel', '123458' );
INSERT INTO briefings (
date, time, location, as_of, instructor_id
) VALUES
( '2023-10-16', '17:00:00', 'Werk Langenhagen', '2021-02-01', '1' ),
( '2023-10-16', '17:05:00', 'Werk Langenhagen', '2021-02-01', '2' );
INSERT INTO participants (
first_name, last_name, company
) VALUES
( 'Peter', 'Enis', 'Körber' ),
( 'Dürüm', 'Döner', 'MP Technic' );
INSERT INTO questions (
question, answer_1, answer_2, answer_3, answer_4, correct_answer
) VALUES
( 'Was ist 1+1?', '1', '2', '3', '4', '2' ),
( 'Was ist 1+2?', '1', '2', '3', '4', '3' ),
( 'Was ist 2+2?', '1', '2', '3', '4', '4' );
INSERT INTO given_answers (
briefing_id, participant_id, question_id, given_answer
) VALUES
( '1', '1', '1', '2' ),
( '1', '1', '2', '3' ),
( '1', '1', '3', '3' ),
( '2', '2', '1', '2' ),
( '2', '2', '2', '3' ),
( '2', '2', '3', '4' );

47
main.go
View File

@ -1,29 +1,60 @@
package main package main
import ( import (
"fmt"
"html/template"
"log" "log"
"net/http" "net/http"
"streifling.com/jason/sicherheitsunterweisung/packages/db" "streifling.com/jason/sicherheitsunterweisung/packages/data"
"streifling.com/jason/sicherheitsunterweisung/packages/server" "streifling.com/jason/sicherheitsunterweisung/packages/server"
"streifling.com/jason/sicherheitsunterweisung/packages/types"
) )
func waitForParticipants(sb []*types.Briefing, sp []*types.Participant, cp <-chan *types.Participant, m *http.ServeMux) {
for p := range cp {
p.Questions = data.InitQuestions()
sp = append(sp, p)
var i int
for i = range p.Questions {
m.HandleFunc("/display-question-"+fmt.Sprintf("%d", p.ID)+"-"+fmt.Sprintf("%d", i)+"/", server.DisplayQuestion(i, p))
}
m.HandleFunc("/display-question-"+fmt.Sprintf("%d", p.ID)+"-"+fmt.Sprintf("%d", i+1)+"/", server.DisplayTestResults(sb, p))
}
}
func main() { func main() {
var i, j int64 logins := make([]string, 0)
i, j = 1, 1 participants := make([]*types.Participant, 0)
briefings := make([]*types.Briefing, 0)
mux := http.NewServeMux() mux := http.NewServeMux()
db, err := db.Open("sicherheitsunterweisung") db, err := data.OpenDB("sicherheitsunterweisung")
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
var i, j int64
if err := db.GetLastID(&i); err != nil {
log.Fatalln(err)
}
j = i
participantChan := make(chan *types.Participant)
defer close(participantChan)
go waitForParticipants(briefings, participants, participantChan, mux)
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static/")))) mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static/"))))
mux.HandleFunc("/", server.DisplayTable(db)) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/submit/", server.SubmitForm(db, &i, &j)) template.Must(template.ParseFiles("templates/index.html", "templates/login.html")).Execute(w, nil)
mux.HandleFunc("/search/", server.DisplayResults(db)) })
mux.HandleFunc("/search/", server.DisplaySearchResults(db))
mux.HandleFunc("/new-briefing/", server.DisplayForm(&i)) mux.HandleFunc("/new-briefing/", server.DisplayForm(&i))
mux.HandleFunc("/add-participant/", server.AddParticipant(&i)) mux.HandleFunc("/add-participant/", server.AddParticipant(&i, &logins))
mux.HandleFunc("/submit-form/", server.SubmitForm(db, &i, &j))
mux.HandleFunc("/internal-login/", server.DisplayTable(db))
mux.HandleFunc("/external-login/", server.DisplayParticipantForm(&logins, participantChan))
log.Fatalln(http.ListenAndServe(":8080", mux)) log.Fatalln(http.ListenAndServe(":8080", mux))
} }

View File

@ -1,4 +1,4 @@
package db package data
import ( import (
"bufio" "bufio"
@ -19,25 +19,54 @@ type DB struct {
Name string Name string
} }
func getCredentials() (string, string, error) { func getUsername() (string, error) {
fmt.Printf("DB Benutzer: ") user := os.Getenv("DB_USER")
user, err := bufio.NewReader(os.Stdin).ReadString('\n') if user == "" {
if err != nil { var err error
return "", "", fmt.Errorf("getCredentials: bufio.NewReader(os.Stdin).ReadString('\n'): %v", err) fmt.Printf("DB Benutzer: ")
user, err = bufio.NewReader(os.Stdin).ReadString('\n')
if err != nil {
return "", fmt.Errorf("getUsername: bufio.NewReader(os.Stdin).ReadString('\n'): %v", err)
}
} }
return strings.TrimSpace(user), nil
fmt.Printf("DB Passwort: ")
bytePass, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return "", "", fmt.Errorf("getCredentials: term.ReadPassword(int(syscall.Stdin)): %v", err)
}
fmt.Println()
pass := string(bytePass)
return strings.TrimSpace(user), strings.TrimSpace(pass), nil
} }
func Open(dbName string) (*DB, error) { func getPassword() (string, error) {
pass := os.Getenv("DB_PASS")
if pass == "" {
fmt.Printf("DB Passwort: ")
bytePass, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return "", fmt.Errorf("getCredentials: term.ReadPassword(int(syscall.Stdin)): %v", err)
}
fmt.Println()
pass = strings.TrimSpace(string(bytePass))
}
return pass, nil
}
func getCredentials() (string, string, error) {
user, err := getUsername()
if err != nil {
return "", "", fmt.Errorf("getCredentials: getUsername(): %v", err)
}
pass, err := getPassword()
if err != nil {
return "", "", fmt.Errorf("getCredentials: getPassword(): %v", err)
}
return user, pass, nil
}
func reverseOrder(bs []*types.Briefing) {
for i, j := 0, len(bs)-1; i < j; i, j = i+1, j-1 {
bs[i], bs[j] = bs[j], bs[i]
}
}
func OpenDB(dbName string) (*DB, error) {
var err error var err error
db := new(DB) db := new(DB)
@ -63,18 +92,18 @@ func Open(dbName string) (*DB, error) {
func (db *DB) WriteBriefing(b *types.Briefing) error { func (db *DB) WriteBriefing(b *types.Briefing) error {
for i := 0; i < len(b.Participants); i++ { for i := 0; i < len(b.Participants); i++ {
result, err := db.Exec("INSERT INTO "+db.Name+" (instructor_first,"+ result, err := db.Exec("INSERT INTO "+db.Name+" (instructor_first,"+
"instructor_last, date, time, state, location, participant_first,"+ " instructor_last, date, time, state, location, participant_first,"+
"participant_last, company) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", " participant_last, company) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
b.FirstName, b.LastName, b.Date, b.Time, b.State, b.Location, b.FirstName, b.LastName, b.Date, b.Time, b.State, b.Location,
b.Participants[i].FirstName, b.Participants[i].LastName, b.Participants[i].FirstName, b.Participants[i].LastName,
b.Participants[i].Company) b.Participants[i].Company)
if err != nil { if err != nil {
return fmt.Errorf("*DB.WriteBriefing: db.Exec(\"INSERT INTO"+ return fmt.Errorf("*DB.WriteBriefing: db.Exec(\"INSERT INTO"+
"\"+db.Name+\" (instructor_first, instructor_last, date, time, state,"+ " \"+db.Name+\" (instructor_first, instructor_last, date, time, state,"+
"location, participant_first, participant_last, company) VALUES (?, ?,"+ " location, participant_first, participant_last, company) VALUES (?, ?,"+
"?, ?, ?, ?, ?, ?, ?)\", b.FirstName, b.LastName, b.Date, b.Time,"+ " ?, ?, ?, ?, ?, ?, ?)\", b.FirstName, b.LastName, b.Date, b.Time,"+
"b.State, b.Location, b.Participants[i].FirstName,"+ " b.State, b.Location, b.Participants[i].FirstName,"+
"b.Participants[i].LastName, b.Participants[i].Company): %v\n", err) " b.Participants[i].LastName, b.Participants[i].Company): %v\n", err)
} }
_, err = result.LastInsertId() _, err = result.LastInsertId()
@ -86,8 +115,8 @@ func (db *DB) WriteBriefing(b *types.Briefing) error {
return nil return nil
} }
func (db *DB) ReadAll() (*[]types.Briefing, error) { func (db *DB) ReadAll() ([]*types.Briefing, error) {
bs := make([]types.Briefing, 0) bs := make([]*types.Briefing, 0)
rows, err := db.Query("SELECT *" + " FROM " + db.Name) rows, err := db.Query("SELECT *" + " FROM " + db.Name)
if err != nil { if err != nil {
@ -104,15 +133,16 @@ func (db *DB) ReadAll() (*[]types.Briefing, error) {
return nil, fmt.Errorf("*DB.ReadAll: db.Query(): %v\n", err) return nil, fmt.Errorf("*DB.ReadAll: db.Query(): %v\n", err)
} }
b.Participants = append(b.Participants, *p) b.Participants = append(b.Participants, p)
bs = append(bs, *b) bs = append(bs, b)
} }
return &bs, nil reverseOrder(bs)
return bs, nil
} }
func (db *DB) ReadByName(name string) (*[]types.Briefing, error) { func (db *DB) ReadByName(name string) ([]*types.Briefing, error) {
bs := make([]types.Briefing, 0) bs := make([]*types.Briefing, 0)
rows, err := db.Query("SELECT *"+ rows, err := db.Query("SELECT *"+
" FROM "+db.Name+ " FROM "+db.Name+
@ -141,9 +171,22 @@ func (db *DB) ReadByName(name string) (*[]types.Briefing, error) {
" &b.LastName, &b.Date, &b.Time, &b.State, &b.Location, &p.FirstName,"+ " &b.LastName, &b.Date, &b.Time, &b.State, &b.Location, &p.FirstName,"+
" &p.LastName, &p.Company): %v\n", err) " &p.LastName, &p.Company): %v\n", err)
} }
b.Participants = append(b.Participants, *p) b.Participants = append(b.Participants, p)
bs = append(bs, *b) bs = append(bs, b)
} }
return &bs, nil reverseOrder(bs)
return bs, nil
}
func (db *DB) GetLastID(i *int64) error {
row := db.QueryRow("SELECT id" +
" FROM " + db.Name +
" ORDER BY id DESC LIMIT 0, 1")
if err := row.Scan(i); err != nil {
return fmt.Errorf("*DB.GetLastID: row.Scan(&i): %v\n", err)
}
return nil
} }

View File

@ -0,0 +1,42 @@
package data
import "streifling.com/jason/sicherheitsunterweisung/packages/types"
func InitQuestions() []types.Question {
Q := make([]types.Question, 0)
Q = append(Q, types.Question{
Text: "Wie viel ist 1+1?",
Answers: []types.Answer{
{ID: 0, Text: "1"},
{ID: 1, Text: "2"},
{ID: 2, Text: "3"},
{ID: 3, Text: "4"},
},
Correct: 1,
})
Q = append(Q, types.Question{
Text: "Wie viel ist 2+2?",
Answers: []types.Answer{
{ID: 0, Text: "1"},
{ID: 1, Text: "2"},
{ID: 2, Text: "3"},
{ID: 3, Text: "4"},
},
Correct: 3,
})
Q = append(Q, types.Question{
Text: "Wie viel ist 1+2?",
Answers: []types.Answer{
{ID: 0, Text: "1"},
{ID: 1, Text: "2"},
{ID: 2, Text: "3"},
{ID: 3, Text: "4"},
},
Correct: 2,
})
return Q
}

View File

@ -1,26 +1,37 @@
package server package server
import ( import (
"crypto/rand"
"encoding/hex"
"fmt" "fmt"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
"strconv"
"strings"
"streifling.com/jason/sicherheitsunterweisung/packages/db" "streifling.com/jason/sicherheitsunterweisung/packages/data"
"streifling.com/jason/sicherheitsunterweisung/packages/types" "streifling.com/jason/sicherheitsunterweisung/packages/types"
) )
func DisplayTable(db *db.DB) http.HandlerFunc { type questionData struct {
ID int64
Q types.Question
I int
J int
}
func DisplayTable(db *data.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
bs, err := db.ReadAll() bs, err := db.ReadAll()
if err != nil { if err != nil {
_ = fmt.Errorf("DisplayTable: %v\n", err) _ = fmt.Errorf("DisplayTable: %v\n", err)
} }
template.Must(template.ParseFiles("templates/index.html", "templates/table.html")).Execute(w, bs) template.Must(template.ParseFiles("templates/table.html")).ExecuteTemplate(w, "content", bs)
} }
} }
func DisplayResults(db *db.DB) http.HandlerFunc { func DisplaySearchResults(db *data.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
bs, err := db.ReadByName(r.PostFormValue("search")) bs, err := db.ReadByName(r.PostFormValue("search"))
if err != nil { if err != nil {
@ -32,18 +43,31 @@ func DisplayResults(db *db.DB) http.HandlerFunc {
func DisplayForm(i *int64) http.HandlerFunc { func DisplayForm(i *int64) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
template.Must(template.ParseFiles("templates/form.html")).ExecuteTemplate(w, "content", i) template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "content", i)
} }
} }
func AddParticipant(i *int64) http.HandlerFunc { func generateUUID() string {
bs := make([]byte, 2)
if _, err := rand.Read(bs); err != nil {
_ = fmt.Errorf("GenerateUUID: rand.Read(bs): %v\n", err)
return ""
}
return hex.EncodeToString(bs)
}
func AddParticipant(i *int64, ls *[]string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
*i++ *i++
template.Must(template.ParseFiles("templates/form.html")).ExecuteTemplate(w, "participant", i) login := fmt.Sprintf("%d", *i) + "-" + generateUUID()
(*ls) = append(*ls, login)
template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "new", login)
} }
} }
func SubmitForm(db *db.DB, i, j *int64) http.HandlerFunc { func SubmitForm(db *data.DB, i, j *int64) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
b := new(types.Briefing) b := new(types.Briefing)
@ -55,7 +79,7 @@ func SubmitForm(db *db.DB, i, j *int64) http.HandlerFunc {
b.Location = r.PostFormValue("location") b.Location = r.PostFormValue("location")
for ; *j <= *i; *j++ { for ; *j <= *i; *j++ {
b.Participants = append(b.Participants, types.Participant{ b.Participants = append(b.Participants, &types.Participant{
ID: *j, ID: *j,
Person: types.Person{ Person: types.Person{
FirstName: r.PostFormValue("participant-first-" + fmt.Sprint(*j)), FirstName: r.PostFormValue("participant-first-" + fmt.Sprint(*j)),
@ -71,6 +95,112 @@ func SubmitForm(db *db.DB, i, j *int64) http.HandlerFunc {
if err != nil { if err != nil {
_ = fmt.Errorf("SubmitForm: db.ReadAll(): %v\n", err) _ = fmt.Errorf("SubmitForm: db.ReadAll(): %v\n", err)
} }
template.Must(template.ParseFiles("templates/index.html", "templates/table.html")).Execute(w, bs)
template.Must(template.ParseFiles("templates/table.html")).Execute(w, bs)
}
}
// TODO: Make it only serve one purpose
func loginIsCorrect(l string, logins *[]string) bool {
for i, v := range *logins {
if l == v {
(*logins) = append((*logins)[:i], (*logins)[i+1:]...)
return true
}
}
return false
}
func newParticipant(l string) (*types.Participant, error) {
p := new(types.Participant)
idInt, err := strconv.Atoi(strings.Split(l, "-")[0])
if err != nil {
return nil, fmt.Errorf("newParticipant: strconv.Atoi(idString): %v\n", err)
}
p.ID = int64(idInt)
return p, nil
}
func DisplayParticipantForm(ls *[]string, cp chan<- *types.Participant) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := r.PostFormValue("login")
if loginIsCorrect(l, ls) {
p, err := newParticipant(l)
if err != nil {
http.Error(w, "GetParticipantData: newParticipant(l): "+fmt.Sprint(err), http.StatusInternalServerError)
}
cp <- p
template.Must(template.ParseFiles("templates/participant.html")).ExecuteTemplate(w, "content", p.ID)
} else {
template.Must(template.ParseFiles("templates/login.html")).ExecuteTemplate(w, "content", nil)
}
}
}
func readAnswer(r *http.Request, p *types.Participant, i int) error {
v, err := strconv.Atoi(r.PostFormValue("answer"))
if err != nil {
return fmt.Errorf("readAnswer: strconv.Atoi(r.PostFormValue(\"answer\")): %v\n", err)
}
p.Questions[i].Chosen = v
return nil
}
func DisplayQuestion(i int, p *types.Participant) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if i == 0 {
p.FirstName = r.PostFormValue("participant-first-" + fmt.Sprintf("%d", p.ID))
p.LastName = r.PostFormValue("participant-last-" + fmt.Sprintf("%d", p.ID))
p.Company = r.PostFormValue("participant-company-" + fmt.Sprintf("%d", p.ID))
} else {
if err := readAnswer(r, p, i-1); err != nil {
http.Error(w, "DisplayQuestion: readAnswer(r, p, i): "+fmt.Sprint(err), http.StatusInternalServerError)
}
}
data := new(questionData)
data.ID = p.ID
data.Q = p.Questions[i]
data.I = i
data.J = i + 1
template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
}
}
func DisplayTestResults(b *types.Briefing, p *types.Participant) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
numQuestions := len(p.Questions)
wrongAnswers := make([]int, 0)
fmt.Println(wrongAnswers)
if err := readAnswer(r, p, numQuestions-1); err != nil {
http.Error(w, "DisplayTestResults: readAnswer(r, p, i): "+fmt.Sprint(err), http.StatusInternalServerError)
}
for i, q := range p.Questions {
if q.Chosen != q.Correct {
wrongAnswers = append(wrongAnswers, i)
}
}
if wrongAnswers == nil {
b.Participants = append(b.Participants, p)
} else {
data := new(questionData)
data.ID = p.ID
data.Q = p.Questions[0]
data.I = 0
data.J = data.I + 1
template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
}
template.Must(template.ParseFiles("templates/results.html")).ExecuteTemplate(w, "content", nil)
} }
} }

View File

@ -7,10 +7,23 @@ type Person struct {
type Instructor Person type Instructor Person
type Answer struct {
ID int
Text string
}
type Question struct {
Text string
Answers []Answer
Chosen int
Correct int
}
type Participant struct { type Participant struct {
ID int64 ID int64
Person Person
Company string Company string
Questions []Question
} }
type Briefing struct { type Briefing struct {
@ -19,5 +32,5 @@ type Briefing struct {
Time string Time string
State string State string
Location string Location string
Participants []Participant Participants []*Participant
} }

38
templates/briefing.html Normal file
View File

@ -0,0 +1,38 @@
{{ define "add-button" }}
<button type="button" hx-post="/add-participant/" hx-target="this" hx-swap="outerHTML">
Neuer Teilnehmer
</button>
{{ end }}
{{ define "new" }}
<span>{{ . }}</span>
{{ template "add-button" . }}
{{ end }}
{{ define "content" }}
<form>
<div id="instructor">
<label for="instructor-first-input">Unterweiser Vorname</label>
<input type="text" name="instructor-first" id="instructor-first-input" />
<label for="instructor-last-input">Unterweiser Nachname</label>
<input type="text" name="instructor-last" id="instructor-last-input" />
</div>
<div id="location">
<label for="location-input">Ort</label>
<input type="text" name="location" id="location-input" />
</div>
<div id="state">
<label for="state-input">Stand vom</label>
<input type="date" name="state" id="state-input" />
</div>
{{ template "add-button" . }}
<button type="submit" hx-post="/submit-form/" hx-target="#content" hx-swap="innerHTML">
OK
</button>
</form>
{{ end }}

View File

@ -1,56 +0,0 @@
{{ define "participant" }}
<div id="participant-{{ . }}">
<label for="participant-first-input-{{ . }}">Vorname</label>
<input type="text" name="participant-first-{{ . }}" id="participant-first-input-{{ . }}" />
<label for="participant-last-input-{{ . }}">Nachname</label>
<input type="text" name="participant-last-{{ . }}" id="participant-last-input-{{ . }}" />
<label for="participant-company-input-{{ . }}">Firma</label>
<input type="text" name="participant-company-{{ . }}" id="participant-company-input-{{ . }}" />
</div>
{{ end }}
{{ define "content" }}
<form>
<div id="instructor">
<label for="instructor-first-input">Unterweiser Vorname</label>
<input type="text" name="instructor-first" id="instructor-first-input" />
<label for="instructor-last-input">Unterweiser Nachname</label>
<input type="text" name="instructor-last" id="instructor-last-input" />
</div>
<div id="date">
<label for="date-input">Datum</label>
<input type="date" name="date" id="date-input" />
</div>
<div id="time">
<label for="time-input">Uhrzeit</label>
<input type="time" name="time" id="time-input" />
</div>
<div id="state">
<label for="state-input">Stand vom</label>
<input type="date" name="state" id="state-input" />
</div>
<div id="location">
<label for="location-input">Ort</label>
<input type="text" name="location" id="location-input" />
</div>
<div id="participants">
<button type="button" hx-post="/add-participant/" hx-target="#participants" hx-swap="beforeend" hx-trigger="click">
+
</button>
{{ template "participant" . }}
</div>
<button type="submit" hx-post="/submit/" hx-target="#content" hx-swap="innerHTML">
Senden
</button>
</form>
{{ end }}

16
templates/login.html Normal file
View File

@ -0,0 +1,16 @@
{{ define "content" }}
<h2>Login</h2>
<form>
<label for="login-input">Code</label>
<input type="text" name="login" id="login-input" />
<button type="submit" hx-post="/external-login/" hx-target="#content">
Anmelden
</button>
<button type="submit" hx-post="/internal-login/" hx-target="#content">
Intern
</button>
</form>
{{ end }}

View File

@ -0,0 +1,16 @@
{{ define "content" }}
<form id="participant-{{ . }}">
<label for="participant-first-input-{{ . }}">Vorname</label>
<input type="text" name="participant-first-{{ . }}" id="participant-first-input-{{ . }}" />
<label for="participant-last-input-{{ . }}">Nachname</label>
<input type="text" name="participant-last-{{ . }}" id="participant-last-input-{{ . }}" />
<label for="participant-company-input-{{ . }}">Firma</label>
<input type="text" name="participant-company-{{ . }}" id="participant-company-input-{{ . }}" />
<button type="button" hx-post="/display-question-{{ . }}-0/" hx-target="#content">
Fertig
</button>
</form>
{{ end }}

21
templates/question.html Normal file
View File

@ -0,0 +1,21 @@
{{ define "answers" }}
{{ range .Q.Answers }}
<div>
<input type="radio" name="answer" id="answer-{{ .ID }}" value="{{ .ID }}" />
<label for="answer-{{ .ID }}">{{ .Text }}</label>
</div>
{{ end }}
{{ end }}
{{ define "content" }}
<h2>Frage {{ .I }}</h2>
<p>{{ .Q.Text }}</p>
<form>
{{ template "answers" . }}
<button type="submit" hx-post="/display-question-{{ .ID }}-{{ .J }}/" hx-target="#content" hx-swap="innerHTML">
Weiter
</button>
</form>
{{ end }}

11
templates/results.html Normal file
View File

@ -0,0 +1,11 @@
{{ define "passed" }}
{{ end }}
{{ define "failed" }}
{{ end }}
{{ define "content" }}
{{ q := range .Participant.Questions }}
<p>{{ . }}{{ q.Text }}</p>
{{ end }}
{{ end }}

View File

@ -19,10 +19,8 @@
{{ define "content" }} {{ define "content" }}
<form> <form>
<label for="search-input">Suche</label> <label for="search-input">Suche</label>
<input type="text" name="search" id="search-input" /> <input type="text" name="search" id="search-input" hx-post="/search/" hx-target="#results" hx-swap="innerHTML"
<button type="submit" hx-post="/search/" hx-target="#results" hx-swap="innerHTML"> hx-trigger="keyup changed delay:200ms" />
Suchen
</button>
</form> </form>
<form> <form>
@ -34,12 +32,12 @@
<table> <table>
<thead> <thead>
<tr> <tr>
<th colspan="2">Name Unterweiser</th> <th colspan="2">Unterweiser</th>
<th>Datum</th> <th>Datum</th>
<th>Uhrzeit</th> <th>Uhrzeit</th>
<th>Stand</th> <th>Stand</th>
<th>Ort</th> <th>Ort</th>
<th colspan="2">Name Teilnehmer</th> <th colspan="2">Teilnehmer</th>
<th>Firma</th> <th>Firma</th>
</tr> </tr>
</thead> </thead>

1
tmp/build-errors.log Normal file
View File

@ -0,0 +1 @@
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1

BIN
tmp/main Executable file

Binary file not shown.