11 Commits

18 changed files with 501 additions and 266 deletions

2
.gitignore vendored
View File

@ -0,0 +1,2 @@
tmp
test.sql

View File

@ -1,21 +1,23 @@
USE sicherheitsunterweisung; USE sicherheitsunterweisung;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS instructors; DROP TABLE IF EXISTS instructors;
DROP TABLE IF EXISTS briefings; DROP TABLE IF EXISTS briefings;
DROP TABLE IF EXISTS participants; DROP TABLE IF EXISTS participants;
DROP TABLE IF EXISTS questions; DROP TABLE IF EXISTS questions;
DROP TABLE IF EXISTS given_answers; DROP TABLE IF EXISTS given_answers;
SET FOREIGN_KEY_CHECKS = 1;
CREATE TABLE instructors ( CREATE TABLE instructors (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL,
first_name VARCHAR(32) NOT NULL, first_name VARCHAR(32) NOT NULL,
last_name VARCHAR(32) NOT NULL, last_name VARCHAR(32) NOT NULL,
personnel_id INT NOT NULL,
PRIMARY KEY(id) PRIMARY KEY(id)
); );
CREATE TABLE briefings ( CREATE TABLE briefings (
id INT NOT NULL AUTO_INCREMENT, id INT AUTO_INCREMENT,
date DATE NOT NULL, date DATE NOT NULL,
time TIME NOT NULL, time TIME NOT NULL,
location VARCHAR(32) NOT NULL, location VARCHAR(32) NOT NULL,
@ -28,7 +30,7 @@ CREATE TABLE briefings (
); );
CREATE TABLE participants ( CREATE TABLE participants (
id INT NOT NULL AUTO_INCREMENT, id INT AUTO_INCREMENT,
first_name VARCHAR(32) NOT NULL, first_name VARCHAR(32) NOT NULL,
last_name VARCHAR(32) NOT NULL, last_name VARCHAR(32) NOT NULL,
company VARCHAR(32) NOT NULL, company VARCHAR(32) NOT NULL,
@ -37,7 +39,7 @@ CREATE TABLE participants (
); );
CREATE TABLE questions ( CREATE TABLE questions (
id INT NOT NULL AUTO_INCREMENT, id INT AUTO_INCREMENT,
question VARCHAR(256) NOT NULL, question VARCHAR(256) NOT NULL,
answer_1 VARCHAR(64) NOT NULL, answer_1 VARCHAR(64) NOT NULL,
answer_2 VARCHAR(64) NOT NULL, answer_2 VARCHAR(64) NOT NULL,
@ -61,17 +63,17 @@ CREATE TABLE given_answers (
); );
INSERT INTO instructors INSERT INTO instructors
(first_name, last_name, personnel_id) (id, first_name, last_name)
VALUES VALUES
( 'Jason', 'Streifling', '123456' ), ( '123456', 'Jason', 'Streifling' ),
( 'Tim', 'Taler', '123457' ), ( '123457', 'Tim', 'Taler' ),
( 'Georg', 'aus dem Jungel', '123458' ); ( '123458', 'Georg', 'aus dem Jungel' );
INSERT INTO briefings ( INSERT INTO briefings (
date, time, location, as_of, instructor_id date, time, location, document_name, as_of, instructor_id
) VALUES ) VALUES
( '2023-10-16', '17:00:00', 'Werk Langenhagen', 'ICS-2021-LGH', '2021-02-01', '1' ), ( '2023-10-16', '17:00:00', 'Werk Langenhagen', 'ICS-2021-LGH', '2021-02-01', '123456' ),
( '2023-10-16', '17:05:00', 'Werk Langenhagen', 'ICS-2021-LGH', '2021-02-01', '2' ); ( '2023-10-16', '17:05:00', 'Werk Langenhagen', 'ICS-2021-LGH', '2021-02-01', '123457' );
INSERT INTO participants ( INSERT INTO participants (
first_name, last_name, company first_name, last_name, company
@ -84,7 +86,8 @@ INSERT INTO questions (
) VALUES ) VALUES
( 'Was ist 1+1?', '1', '2', '3', '4', '2' ), ( 'Was ist 1+1?', '1', '2', '3', '4', '2' ),
( 'Was ist 1+2?', '1', '2', '3', '4', '3' ), ( 'Was ist 1+2?', '1', '2', '3', '4', '3' ),
( 'Was ist 2+2?', '1', '2', '3', '4', '4' ); ( 'Was ist 2+2?', '1', '2', '3', '4', '4' ),
( 'Was ist 0+1?', '1', '2', '3', '4', '1' );
INSERT INTO given_answers ( INSERT INTO given_answers (
briefing_id, participant_id, question_id, given_answer briefing_id, participant_id, question_id, given_answer
@ -92,6 +95,8 @@ INSERT INTO given_answers (
( '1', '1', '1', '2' ), ( '1', '1', '1', '2' ),
( '1', '1', '2', '3' ), ( '1', '1', '2', '3' ),
( '1', '1', '3', '3' ), ( '1', '1', '3', '3' ),
( '1', '1', '4', '1' ),
( '2', '2', '1', '2' ), ( '2', '2', '1', '2' ),
( '2', '2', '2', '3' ), ( '2', '2', '2', '3' ),
( '2', '2', '3', '4' ); ( '2', '2', '3', '4' ),
( '2', '2', '4', '1' );

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.21.1
require ( require (
github.com/go-sql-driver/mysql v1.7.1 github.com/go-sql-driver/mysql v1.7.1
github.com/google/uuid v1.3.1
golang.org/x/term v0.13.0 golang.org/x/term v0.13.0
) )

2
go.sum
View File

@ -1,5 +1,7 @@
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= 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/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=

56
main.go
View File

@ -1,33 +1,69 @@
package main package main
import ( import (
"fmt"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
"streifling.com/jason/sicherheitsunterweisung/packages/data" "streifling.com/jason/sicherheitsunterweisung/packages/db"
"streifling.com/jason/sicherheitsunterweisung/packages/server" "streifling.com/jason/sicherheitsunterweisung/packages/server"
"streifling.com/jason/sicherheitsunterweisung/packages/types"
) )
func main() { func handleParticipants(mux *http.ServeMux, db *db.DB, cp <-chan *types.Participant, s *types.Session) {
logins := make([]string, 0) for participant := range cp {
mux.HandleFunc("/submit-participant/"+fmt.Sprint(s.ID)+"/"+fmt.Sprint(participant.Login)+"/", server.HandleParticipant(s, participant, &s.Questions, db))
for i := range s.Questions {
mux.HandleFunc("/submit-answer/"+fmt.Sprint(s.ID)+"/"+fmt.Sprint(participant.Login)+"/"+fmt.Sprint(i+1)+"/", server.HandleAnswer(s, db, participant, &s.Questions, int64(i+1)))
}
mux.HandleFunc("/retry/"+fmt.Sprint(s.ID)+"/"+fmt.Sprint(participant.Login)+"/", server.HandleRetry(s, participant, &s.Questions))
}
}
db, err := data.OpenDB("sicherheitsunterweisung") func handleSessions(mux *http.ServeMux, db *db.DB, cs <-chan *types.Session, ss *[]*types.Session) {
for session := range cs {
(*ss) = append(*ss, session)
participantChan := make(chan *types.Participant)
questionIDs := make([]string, 4)
for i := 0; i < len(questionIDs); i++ {
questionIDs[i] = fmt.Sprint(i + 1)
}
var err error
session.Questions, err = db.GetQuestions(questionIDs)
if err != nil {
log.Fatalln(err)
}
mux.HandleFunc("/new-briefing/", server.HandleNewBriefing(session))
mux.HandleFunc("/new-participant/"+fmt.Sprint(session.ID)+"/", server.HandleNewParticipant(session, participantChan))
mux.HandleFunc("/submit-form/"+fmt.Sprint(session.ID)+"/", server.HandleBriefingForm(session, db))
go handleParticipants(mux, db, participantChan, session)
}
}
func main() {
db, err := db.Open("sicherheitsunterweisung")
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
mux := http.NewServeMux() mux := http.NewServeMux()
sessions := make([]*types.Session, 0)
sessionChan := make(chan *types.Session)
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static/")))) mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static/"))))
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
template.Must(template.ParseFiles("templates/index.html", "templates/login.html")).Execute(w, nil) template.Must(template.ParseFiles("templates/index.html", "templates/login.html")).Execute(w, nil)
}) })
mux.HandleFunc("/search/", server.DisplaySearchResults(db)) mux.HandleFunc("/internal-login/", server.HandleInternalLogin(&sessions, sessionChan, db))
mux.HandleFunc("/new-briefing/", server.DisplayInstructorForm()) mux.HandleFunc("/external-login/", server.HandleExternalLogin(&sessions))
mux.HandleFunc("/add-participant/", server.AddParticipant(&logins)) mux.HandleFunc("/search/", server.HandleSearch(db))
mux.HandleFunc("/submit-form/", server.SubmitBriefingForm(db, &logins))
mux.HandleFunc("/internal-login/", server.DisplayTable(db)) go handleSessions(mux, db, sessionChan, &sessions)
mux.HandleFunc("/external-login/", server.DisplayParticipantForm(&logins))
log.Fatalln(http.ListenAndServe(":8080", mux)) log.Fatalln(http.ListenAndServe(":8080", mux))
} }

View File

@ -1,42 +0,0 @@
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,4 +1,4 @@
package data package db
import ( import (
"bufio" "bufio"
@ -8,10 +8,9 @@ import (
"strings" "strings"
"syscall" "syscall"
"streifling.com/jason/sicherheitsunterweisung/packages/types"
"github.com/go-sql-driver/mysql" "github.com/go-sql-driver/mysql"
"golang.org/x/term" "golang.org/x/term"
"streifling.com/jason/sicherheitsunterweisung/packages/types"
) )
type DB struct { type DB struct {
@ -60,7 +59,7 @@ func getCredentials() (string, string, error) {
return user, pass, nil return user, pass, nil
} }
func OpenDB(dbName string) (*DB, error) { func Open(dbName string) (*DB, error) {
var err error var err error
db := new(DB) db := new(DB)
@ -121,7 +120,7 @@ func (db *DB) WriteParticipant(p *types.Participant) error {
return nil return nil
} }
func (db *DB) WriteGivenAnswer(b *types.Briefing, p *types.Participant, q *types.Question, g *types.GivenAnswer) error { func (db *DB) WriteGivenAnswer(b *types.Briefing, p *types.Participant, q *types.Question, g int) error {
_, err := db.Exec(` _, err := db.Exec(`
INSERT INTO given_answers INSERT INTO given_answers
(briefing_id, participant_id, question_id, given_answer) (briefing_id, participant_id, question_id, given_answer)
@ -148,14 +147,14 @@ func (db *DB) WriteAllDataOfBriefing(b *types.Briefing, sp *[]*types.Participant
for _, p := range *sp { for _, p := range *sp {
for i, q := range *sq { for i, q := range *sq {
db.WriteGivenAnswer(b, p, q, (*sg)[i]) db.WriteGivenAnswer(b, p, q, i)
} }
} }
return nil return nil
} }
func (db *DB) GetAllOverviewTableData() (*[]*types.OverviewTableData, error) { func (db *DB) GetAllOverviewTableData() ([]*types.OverviewTableData, error) {
rows, err := db.Query(` rows, err := db.Query(`
SELECT SELECT
i.first_name, i.first_name,
@ -173,8 +172,12 @@ func (db *DB) GetAllOverviewTableData() (*[]*types.OverviewTableData, error) {
ON b.id = g.briefing_id ON b.id = g.briefing_id
INNER JOIN participants AS p INNER JOIN participants AS p
ON p.id = g.participant_id ON p.id = g.participant_id
INNER JOIN questions AS q
ON q.id = g.question_id
INNER JOIN instructors AS i INNER JOIN instructors AS i
ON i.id = b.instructor_id ON i.id = b.instructor_id
WHERE
q.id = 1
ORDER BY ORDER BY
b.id DESC, b.id DESC,
p.id p.id
@ -207,7 +210,7 @@ func (db *DB) GetAllOverviewTableData() (*[]*types.OverviewTableData, error) {
data = append(data, otd) data = append(data, otd)
} }
return &data, nil return data, nil
} }
func (db *DB) GetOverviewTableDataByName(n string) (*[]*types.OverviewTableData, error) { func (db *DB) GetOverviewTableDataByName(n string) (*[]*types.OverviewTableData, error) {
@ -231,6 +234,7 @@ func (db *DB) GetOverviewTableDataByName(n string) (*[]*types.OverviewTableData,
INNER JOIN instructors AS i INNER JOIN instructors AS i
ON i.id = b.instructor_id ON i.id = b.instructor_id
WHERE WHERE
q.id = 1 AND
i.first_name LIKE ? OR i.first_name LIKE ? OR
i.last_name LIKE ? OR i.last_name LIKE ? OR
p.first_name LIKE ? OR p.first_name LIKE ? OR
@ -287,23 +291,91 @@ func (db *DB) GetLastID(table string) (int, error) {
return id, nil return id, nil
} }
func (db *DB) GetInstructors() (*[]*types.Instructor, error) { func (db *DB) GetInstructors() ([]*types.Instructor, error) {
rows, err := db.Query(` rows, err := db.Query(`
SELECT * SELECT *
FROM instructors FROM instructors
ORDER BY
last_name,
first_name
`) `)
if err != nil { if err != nil {
return nil, fmt.Errorf("*DB.GetInstructors: db.Query(): %v\n", err) return nil, fmt.Errorf("*DB.GetInstructors: db.Query(): %v\n", err)
} }
defer rows.Close()
instructors := make([]*types.Instructor, 0) instructors := make([]*types.Instructor, 0)
for rows.Next() { for rows.Next() {
instructor := new(types.Instructor) instructor := new(types.Instructor)
if err = rows.Scan(instructor); err != nil { if err = rows.Scan(&instructor.ID, &instructor.FirstName, &instructor.LastName); err != nil {
return nil, fmt.Errorf("*DB.GetInstructors: rows.Scan(): %v\n", err) return nil, fmt.Errorf("*DB.GetInstructors: rows.Scan(): %v\n", err)
} }
instructors = append(instructors, instructor) instructors = append(instructors, instructor)
} }
return &instructors, nil return instructors, nil
}
func (db *DB) GetQuestions(nums []string) ([]types.Question, error) {
rows, err := db.Query(`
SELECT *
FROM questions
WHERE id IN (` + strings.Join(nums, ", ") + `)
`)
if err != nil {
return nil, fmt.Errorf("*DB.GetQuestions: db.Query(): %v\n", err)
}
defer rows.Close()
questions := make([]types.Question, 0)
for rows.Next() {
q := new(types.Question)
a1 := new(types.Answer)
a2 := new(types.Answer)
a3 := new(types.Answer)
a4 := new(types.Answer)
a1.ID = 1
a2.ID = 2
a3.ID = 3
a4.ID = 4
if err := rows.Scan(&q.ID, &q.Text, &a1.Text, &a2.Text, &a3.Text, &a4.Text, &q.Correct); err != nil {
return nil, fmt.Errorf("*DB.GetQuestions: rows.Scan(): %v\n", err)
}
q.Answers = append(q.Answers, *a1)
q.Answers = append(q.Answers, *a2)
q.Answers = append(q.Answers, *a3)
q.Answers = append(q.Answers, *a4)
questions = append(questions, *q)
}
return questions, nil
}
func (db *DB) GetGivenAnswers(bid, pid int64, sq []types.Question) ([]int, error) {
answers := make([]int, 0)
query := `
SELECT given_answer
FROM given_answers
WHERE
briefing_id = ? AND
participant_id = ? AND
question_id = ?
`
for _, q := range sq {
var answer int
row := db.QueryRow(query, bid, pid, q.ID)
if err := row.Scan(&answer); err != nil {
return nil, fmt.Errorf("*DB.GetGivenAnswers: row.Scan(): %v\n", err)
}
answers = append(answers, answer)
}
return answers, nil
} }

View File

@ -5,32 +5,50 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"html/template" "html/template"
"log"
"net/http" "net/http"
"strconv" "strconv"
"time" "time"
"streifling.com/jason/sicherheitsunterweisung/packages/data" "github.com/google/uuid"
"streifling.com/jason/sicherheitsunterweisung/packages/db"
"streifling.com/jason/sicherheitsunterweisung/packages/types" "streifling.com/jason/sicherheitsunterweisung/packages/types"
) )
// type questionData struct { func displayTable(w http.ResponseWriter, db *db.DB) {
// ID int64 bs, err := db.GetAllOverviewTableData()
// Q types.Question if err != nil {
// I int http.Error(w, "displayTable: *DB.GetAllOverviewTableData(): "+fmt.Sprint(err), http.StatusInternalServerError)
// J int }
// } template.Must(template.ParseFiles("templates/table.html")).ExecuteTemplate(w, "content", bs)
}
func DisplayTable(db *data.DB) http.HandlerFunc { func HandleInternalLogin(ss *[]*types.Session, cs chan<- *types.Session, db *db.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
bs, err := db.GetAllOverviewTableData() instructors, err := db.GetInstructors()
if err != nil { if err != nil {
http.Error(w, "DisplayTable: *DB.GetAllOverviewTableData(): "+fmt.Sprint(err), http.StatusInternalServerError) http.Error(w, "HandleInternalLogin: db.GetInstructors(): "+fmt.Sprint(err), http.StatusInternalServerError)
log.Panicln(err)
} }
template.Must(template.ParseFiles("templates/table.html")).ExecuteTemplate(w, "content", bs)
for _, i := range instructors {
if r.PostFormValue("login") == fmt.Sprint(i.ID) {
session := new(types.Session)
session.ID = uuid.New()
session.Briefing = new(types.Briefing)
session.Briefing.InstructorID = i.ID
(*ss) = append((*ss), session)
cs <- session
displayTable(w, db)
return
}
}
template.Must(template.ParseFiles("templates/login.html")).ExecuteTemplate(w, "content", nil)
} }
} }
func DisplaySearchResults(db *data.DB) http.HandlerFunc { func HandleSearch(db *db.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
bs, err := db.GetOverviewTableDataByName(r.PostFormValue("search")) bs, err := db.GetOverviewTableDataByName(r.PostFormValue("search"))
if err != nil { if err != nil {
@ -40,65 +58,88 @@ func DisplaySearchResults(db *data.DB) http.HandlerFunc {
} }
} }
func DisplayInstructorForm() http.HandlerFunc { func HandleNewBriefing(s *types.Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "content", nil) type httpData struct {
SessionID uuid.UUID
}
data := new(httpData)
data.SessionID = s.ID
template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "content", data)
} }
} }
func generateUUID() (string, error) { func generateLogin() (string, error) {
bs := make([]byte, 4) bs := make([]byte, 4)
if _, err := rand.Read(bs); err != nil { if _, err := rand.Read(bs); err != nil {
return "", fmt.Errorf("GenerateUUID: rand.Read(bs): %v\n", err) return "", fmt.Errorf("generateLogin: rand.Read(bs): %v\n", err)
} }
return hex.EncodeToString(bs), nil return hex.EncodeToString(bs), nil
} }
func AddParticipant(sl *[]string) http.HandlerFunc { func HandleNewParticipant(s *types.Session, cp chan<- *types.Participant) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
login, err := generateUUID() type httpData struct {
if err != nil { SessionID uuid.UUID
http.Error(w, "AddParticipant: generateUUID(): "+fmt.Sprint(err), http.StatusInternalServerError) Login string
} }
(*sl) = append(*sl, login) data := new(httpData)
template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "new", login) var err error
p := new(types.Participant)
p.Login, err = generateLogin()
if err != nil {
http.Error(w, "AddParticipant: generateLogin(): "+fmt.Sprint(err), http.StatusInternalServerError)
}
s.Participants = append(s.Participants, p)
cp <- p
data.SessionID = s.ID
data.Login = p.Login
if err != nil {
http.Error(w, "AddParticipant: generateLogin(): "+fmt.Sprint(err), http.StatusInternalServerError)
}
template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "new", data)
} }
} }
// TODO: Hier weiter machen, irgendwie die b.ID herausgeben, func HandleBriefingForm(s *types.Session, db *db.DB) http.HandlerFunc {
// am besten hier auch die p.IDs rausgeben, damit diese später verknüpft werden können
func SubmitBriefingForm(db *data.DB, sl *[]string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
now := time.Now() now := time.Now()
briefing := new(types.Briefing) var err error
// TODO: Dropdownmenü s.Briefing.Date = now.Format("2006-01-02")
// instructorFirstName := r.PostFormValue("instructor-first") s.Briefing.Time = now.Format("15:04:05")
// instructorLastName := r.PostFormValue("instructor-last") s.Briefing.Location = r.PostFormValue("location")
s.Briefing.DocumentName = r.PostFormValue("document")
s.Briefing.AsOf = r.PostFormValue("as-of")
briefing.Date = now.Format("2006-01-02") err = db.WriteBriefing(s.Briefing)
briefing.Time = now.Format("15:04:05") if err != nil {
briefing.Location = r.PostFormValue("location") http.Error(w, "SubmitBriefingForm: db.WriteBriefing(): "+fmt.Sprint(err), http.StatusInternalServerError)
briefing.DocumentName = r.PostFormValue("document") // TODO: in HTML einfügen log.Panicln(err)
briefing.AsOf = r.PostFormValue("state") // TODO: Umbenennen }
// briefing.InstructorID = r.PostFormValue("instructor-id") // TODO: aus Dropdown holen
db.WriteBriefing(briefing) displayTable(w, db)
} }
} }
// TODO: Make it only serve one purpose // TODO: Make it only serve one purpose
func loginIsCorrect(l string, logins *[]string) bool { func findCorrectLogin(l string, ss *[]*types.Session) (*types.Session, *types.Participant, bool) {
for i, v := range *logins { for _, session := range *ss {
if l == v { for _, p := range session.Participants {
(*logins) = append((*logins)[:i], (*logins)[i+1:]...) if l == p.Login {
return true return session, p, true
}
} }
} }
return false return nil, nil, false
} }
func newParticipant(l string) (*types.Participant, error) { func newParticipant(l string) (*types.Participant, error) {
@ -113,81 +154,176 @@ func newParticipant(l string) (*types.Participant, error) {
return p, nil return p, nil
} }
func DisplayParticipantForm(sl *[]string) http.HandlerFunc { func HandleExternalLogin(ss *[]*types.Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if loginIsCorrect(r.PostFormValue("login"), sl) { type httpData struct {
uuid, err := generateUUID() SessionID uuid.UUID
if err != nil { Login string
http.Error(w, "DisplayParticipantForm: generateUUID(): "+fmt.Sprint(err), http.StatusInternalServerError) }
}
template.Must(template.ParseFiles("templates/participant.html")).ExecuteTemplate(w, "content", uuid) session, participant, loginCorrect := findCorrectLogin(r.PostFormValue("login"), ss)
if loginCorrect {
data := new(httpData)
data.SessionID = session.ID
data.Login = participant.Login
template.Must(template.ParseFiles("templates/participant.html")).ExecuteTemplate(w, "content", data)
} else { } else {
template.Must(template.ParseFiles("templates/login.html")).ExecuteTemplate(w, "content", nil) template.Must(template.ParseFiles("templates/login.html")).ExecuteTemplate(w, "content", nil)
} }
} }
} }
// func readAnswer(r *http.Request, p *types.Participant, i int) error { func handleGivenAnswer(s *types.Session, p *types.Participant, i int64, r *http.Request, db *db.DB) error {
// v, err := strconv.Atoi(r.PostFormValue("answer")) answer, err := strconv.Atoi(r.PostFormValue("answer"))
// if err != nil { if err != nil {
// return fmt.Errorf("readAnswer: strconv.Atoi(): %v\n", err) return fmt.Errorf("handleGivenAnswer: strconv.Atoi(): %v\n", err)
// } }
//
// p.Questions[i].Chosen = v if err := db.WriteGivenAnswer(s.Briefing, p, &s.Questions[i], answer); err != nil {
// return fmt.Errorf("handleGivenAnswer: db.WriteGivenAnswer(): %v\n", err)
// return nil }
// }
// return nil
// func DisplayQuestion(i int, p *types.Participant) http.HandlerFunc { }
// return func(w http.ResponseWriter, r *http.Request) {
// if i == 0 { func HandleParticipant(s *types.Session, p *types.Participant, sq *[]types.Question, db *db.DB) http.HandlerFunc {
// p.FirstName = r.PostFormValue("participant-first-" + fmt.Sprintf("%d", p.ID)) return func(w http.ResponseWriter, r *http.Request) {
// p.LastName = r.PostFormValue("participant-last-" + fmt.Sprintf("%d", p.ID)) type httpData struct {
// p.Company = r.PostFormValue("participant-company-" + fmt.Sprintf("%d", p.ID)) SessionID uuid.UUID
// } else { Login string
// if err := readAnswer(r, p, i-1); err != nil { Question types.Question
// http.Error(w, "DisplayQuestion: readAnswer(r, p, i): "+fmt.Sprint(err), http.StatusInternalServerError) QuestionID int64
// } }
// }
// p.FirstName = r.PostFormValue("first-" + fmt.Sprint(p.Login))
// data := new(questionData) p.LastName = r.PostFormValue("last-" + fmt.Sprint(p.Login))
// data.ID = p.ID p.Company = r.PostFormValue("company-" + fmt.Sprint(p.Login))
// data.Q = p.Questions[i]
// data.I = i err := db.WriteParticipant(p)
// data.J = i + 1 if err != nil {
// http.Error(w, "DisplayQuestion: db.WriteParticipant(): "+fmt.Sprint(err), http.StatusInternalServerError)
// template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data) log.Panicln(err)
// } }
// }
// data := new(httpData)
// func DisplayTestResults(b *types.Briefing, p *types.Participant) http.HandlerFunc { data.SessionID = s.ID
// return func(w http.ResponseWriter, r *http.Request) { data.Login = p.Login
// numQuestions := len(p.Questions) data.Question = (*sq)[0]
// wrongAnswers := make([]int, 0) data.QuestionID = 1
// fmt.Println(wrongAnswers)
// template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
// if err := readAnswer(r, p, numQuestions-1); err != nil { }
// http.Error(w, "DisplayTestResults: readAnswer(r, p, i): "+fmt.Sprint(err), http.StatusInternalServerError) }
// }
// func HandleAnswer(s *types.Session, db *db.DB, p *types.Participant, sq *[]types.Question, i int64) http.HandlerFunc {
// for i, q := range p.Questions { return func(w http.ResponseWriter, r *http.Request) {
// if q.Chosen != q.Correct { log.Println(i, len(*sq))
// wrongAnswers = append(wrongAnswers, i) if i < int64(len(*sq)) {
// } type httpData struct {
// } SessionID uuid.UUID
// Login string
// if wrongAnswers == nil { Question types.Question
// b.Participants = append(b.Participants, p) QuestionID int64
// } else { }
// data := new(questionData)
// data.ID = p.ID if err := handleGivenAnswer(s, p, i-1, r, db); err != nil {
// data.Q = p.Questions[0] http.Error(w, "DisplayQuestion: handleGivenAnswer(): "+fmt.Sprint(err), http.StatusInternalServerError)
// data.I = 0 log.Panicln(err)
// data.J = data.I + 1 }
// template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
// } data := new(httpData)
// data.SessionID = s.ID
// template.Must(template.ParseFiles("templates/results.html")).ExecuteTemplate(w, "content", nil) data.Login = p.Login
// } data.Question = (*sq)[i]
// } data.QuestionID = i + 1
template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
} else {
type answer struct {
Text string
Correct bool
Chosen bool
}
type question struct {
Text string
Answers []answer
}
type httpData struct {
SessionID uuid.UUID
Login string
Questions []question
Incorrect int
}
if err := handleGivenAnswer(s, p, i-1, r, db); err != nil {
http.Error(w, "DisplayTestResults: handleGivenAnswer(): "+fmt.Sprint(err), http.StatusInternalServerError)
log.Panicln(err)
}
givenAnswers, err := db.GetGivenAnswers(s.Briefing.ID, p.ID, s.Questions)
if err != nil {
http.Error(w, "DisplayTestResults: db.GetGivenAnswers(): "+fmt.Sprint(err), http.StatusInternalServerError)
log.Panicln(err)
}
data := new(httpData)
data.SessionID = s.ID
data.Login = p.Login
data.Incorrect = 0
data.Questions = make([]question, 0)
for i, q := range s.Questions {
question := new(question)
question.Text = q.Text
question.Answers = make([]answer, 0)
for j, a := range q.Answers {
answer := new(answer)
answer.Text = a.Text
if j+1 == q.Correct {
answer.Correct = true
} else {
answer.Correct = false
}
if j+1 == givenAnswers[i] {
answer.Chosen = true
} else {
answer.Chosen = false
}
question.Answers = append(question.Answers, *answer)
}
data.Questions = append(data.Questions, *question)
if givenAnswers[i] != q.Correct {
data.Incorrect++
}
}
template.Must(template.ParseFiles("templates/results.html")).ExecuteTemplate(w, "content", data)
}
}
}
func HandleRetry(s *types.Session, p *types.Participant, sq *[]types.Question) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
type httpData struct {
SessionID uuid.UUID
Login string
Question types.Question
QuestionID int64
}
data := new(httpData)
data.SessionID = s.ID
data.Login = p.Login
data.Question = (*sq)[0]
data.QuestionID = 1
template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
}
}

View File

@ -1,5 +1,7 @@
package types package types
import "github.com/google/uuid"
type Person struct { type Person struct {
ID int64 ID int64
FirstName string FirstName string
@ -11,6 +13,7 @@ type Instructor Person
type Participant struct { type Participant struct {
Person Person
Company string Company string
Login string
} }
type Briefing struct { type Briefing struct {
@ -54,3 +57,10 @@ type OverviewTableData struct {
ParticipantLastName string ParticipantLastName string
ParticipantCompany string ParticipantCompany string
} }
type Session struct {
ID uuid.UUID
*Briefing
Participants []*Participant
Questions []Question
}

7
static/css/style.css Normal file
View File

@ -0,0 +1,7 @@
.correct {
color: #00ff00;
}
.incorrect {
color: #ff0000;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,18 @@
{{ define "passed" }} {{define "answers"}}
{{ end }} {{range .Answers}}
<p class="{{if and .Chosen .Correct}} correct {{else if and .Chosen (not .Correct)}} incorrect {{end}}">
{{.Text}}
</p>
{{end}}
{{end}}
{{ define "failed" }} {{define "content"}}
{{ end }} <p>{{.Incorrect}} Fehler</p>
{{range .Questions}}
{{ define "content" }} <p>{{.Text}}</p>
{{ q := range .Participant.Questions }} {{template "answers" .}}
<p>{{ . }}{{ q.Text }}</p> {{end}}
{{ end }} {{if gt .Incorrect 0}}
{{ end }} <button hx-post="/retry/{{.SessionID}}/{{.Login}}/" hx-target="#content" type="submit">Wiederholen</button>
{{end}}
{{end}}

View File

@ -1,17 +1,16 @@
{{ define "rows" }} {{ define "rows" }}
{{ range . }} {{ range . }}
<tr> <tr>
<td>{{ .FirstName }}</td> <td>{{ .InstructorFirstName }}</td>
<td>{{ .LastName }}</td> <td>{{ .InstructorLastName }}</td>
<td>{{ .Date }}</td> <td>{{ .BriefingDate }}</td>
<td>{{ .Time }}</td> <td>{{ .BriefingTime }}</td>
<td>{{ .State }}</td> <td>{{ .BriefingLocation }}</td>
<td>{{ .Location }}</td> <td>{{ .BriefingDocumentName }}</td>
{{ range .Participants }} <td>{{ .BriefingAsOf }}</td>
<td>{{ .FirstName }}</td> <td>{{ .ParticipantFirstName }}</td>
<td>{{ .LastName }}</td> <td>{{ .ParticipantLastName }}</td>
<td>{{ .Company }}</td> <td>{{ .ParticipantCompany }}</td>
{{ end }}
</tr> </tr>
{{ end }} {{ end }}
{{ end }} {{ end }}
@ -35,8 +34,9 @@
<th colspan="2">Unterweiser</th> <th colspan="2">Unterweiser</th>
<th>Datum</th> <th>Datum</th>
<th>Uhrzeit</th> <th>Uhrzeit</th>
<th>Stand</th>
<th>Ort</th> <th>Ort</th>
<th>Dokument</th>
<th>Stand</th>
<th colspan="2">Teilnehmer</th> <th colspan="2">Teilnehmer</th>
<th>Firma</th> <th>Firma</th>
</tr> </tr>

View File

@ -1 +0,0 @@
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

Binary file not shown.