19 Commits

Author SHA1 Message Date
ba53bba0f6 Sessions vollständig implementiert 2023-10-30 11:38:56 +01:00
33cbf4b215 Kleine Bugfixes und Aufräumarbeiten 2023-10-28 10:52:06 +02:00
34ef95ad76 main aufgeräumt, indem mux nach session ausgelagert wurde 2023-10-28 09:17:10 +02:00
e3fb756bb1 DB konstant als ersten Funktionsparameter gesetzt 2023-10-28 09:03:19 +02:00
387f662355 DB.WriteAllDataOfBriefing wurde nicht verwendet und deshalb entfernt 2023-10-28 08:59:22 +02:00
3496fe5f86 package data weiter aufgeräumt 2023-10-28 08:57:15 +02:00
10247722c8 dbStructs.go zu dataStructs.go umbenannt 2023-10-28 08:50:32 +02:00
c7bb630043 Im Grunde nach MVC umstrukturiert und Funktionen in Handlers, die eine Session als Parameter erhalten haben zu Sessionmethoden umgewandelt 2023-10-28 08:49:28 +02:00
5049db064c Der Umstieg auf Sessions ist weitgehend geglückt 2023-10-28 08:01:34 +02:00
aded71394d Kleine Änderungen für Sessions 2023-10-26 10:05:21 +02:00
d054b3644b Instructor Form zum Laufen gebracht 2023-10-20 16:33:00 +02:00
b78e30d109 Größtenteils unwichtiger Kram 2023-10-19 20:06:57 +02:00
52cd5756d8 Kleine Fehlerkorrekturen 2023-10-19 20:06:28 +02:00
1e6d83de1d Sessionhandler eingefügt und Code entsprechend umgestellt 2023-10-19 20:06:08 +02:00
4de7d36385 server weitgehend auf Sessions umgestellt 2023-10-19 20:05:26 +02:00
35d565ec7d Paket data zu db umbenannt, den type Question zur SQL-Abfrage hinzugefügt, um Duplikate zu vermeiden und db.OpenDB() zu db.Open() umbenannt 2023-10-19 20:04:32 +02:00
6359caf3e9 tmp Dir von air entfernt 2023-10-19 20:02:04 +02:00
8622f81f89 table.html auf neue Struktur umgestellt 2023-10-19 19:59:38 +02:00
4e0c8ec1ac type Session hinzugefügt 2023-10-19 19:59:11 +02:00
28 changed files with 1036 additions and 675 deletions

2
.gitignore vendored
View File

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

View File

@ -1,21 +1,23 @@
USE sicherheitsunterweisung;
SET FOREIGN_KEY_CHECKS = 0;
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;
SET FOREIGN_KEY_CHECKS = 1;
CREATE TABLE instructors (
id INT NOT NULL AUTO_INCREMENT,
id INT NOT NULL,
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,
id INT AUTO_INCREMENT,
date DATE NOT NULL,
time TIME NOT NULL,
location VARCHAR(32) NOT NULL,
@ -28,7 +30,7 @@ CREATE TABLE briefings (
);
CREATE TABLE participants (
id INT NOT NULL AUTO_INCREMENT,
id INT AUTO_INCREMENT,
first_name VARCHAR(32) NOT NULL,
last_name VARCHAR(32) NOT NULL,
company VARCHAR(32) NOT NULL,
@ -37,7 +39,7 @@ CREATE TABLE participants (
);
CREATE TABLE questions (
id INT NOT NULL AUTO_INCREMENT,
id INT AUTO_INCREMENT,
question VARCHAR(256) NOT NULL,
answer_1 VARCHAR(64) NOT NULL,
answer_2 VARCHAR(64) NOT NULL,
@ -61,17 +63,17 @@ CREATE TABLE given_answers (
);
INSERT INTO instructors
(first_name, last_name, personnel_id)
(id, first_name, last_name)
VALUES
( 'Jason', 'Streifling', '123456' ),
( 'Tim', 'Taler', '123457' ),
( 'Georg', 'aus dem Jungel', '123458' );
( '123456', 'Jason', 'Streifling' ),
( '123457', 'Tim', 'Taler' ),
( '123458', 'Georg', 'aus dem Jungel' );
INSERT INTO briefings (
date, time, location, as_of, instructor_id
date, time, location, document_name, as_of, instructor_id
) VALUES
( '2023-10-16', '17:00:00', 'Werk Langenhagen', 'ICS-2021-LGH', '2021-02-01', '1' ),
( '2023-10-16', '17:05:00', 'Werk Langenhagen', 'ICS-2021-LGH', '2021-02-01', '2' );
( '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', '123457' );
INSERT INTO participants (
first_name, last_name, company
@ -84,7 +86,8 @@ INSERT INTO questions (
) 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' );
( 'Was ist 2+2?', '1', '2', '3', '4', '4' ),
( 'Was ist 0+1?', '1', '2', '3', '4', '1' );
INSERT INTO given_answers (
briefing_id, participant_id, question_id, given_answer
@ -92,6 +95,8 @@ INSERT INTO given_answers (
( '1', '1', '1', '2' ),
( '1', '1', '2', '3' ),
( '1', '1', '3', '3' ),
( '1', '1', '4', '1' ),
( '2', '2', '1', '2' ),
( '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 (
github.com/go-sql-driver/mysql v1.7.1
github.com/google/uuid v1.3.1
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/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=

19
main.go
View File

@ -6,28 +6,27 @@ import (
"net/http"
"streifling.com/jason/sicherheitsunterweisung/packages/data"
"streifling.com/jason/sicherheitsunterweisung/packages/server"
"streifling.com/jason/sicherheitsunterweisung/packages/session"
)
func main() {
logins := make([]string, 0)
db, err := data.OpenDB("sicherheitsunterweisung")
if err != nil {
log.Fatalln(err)
}
mux := http.NewServeMux()
mux := session.NewMux()
sessions := make([]*session.Session, 0)
sessionChan := make(chan *session.Session)
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static/"))))
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
template.Must(template.ParseFiles("templates/index.html", "templates/login.html")).Execute(w, nil)
})
mux.HandleFunc("/search/", server.DisplaySearchResults(db))
mux.HandleFunc("/new-briefing/", server.DisplayInstructorForm())
mux.HandleFunc("/add-participant/", server.AddParticipant(&logins))
mux.HandleFunc("/submit-form/", server.SubmitBriefingForm(db, &logins))
mux.HandleFunc("/internal-login/", server.DisplayTable(db))
mux.HandleFunc("/external-login/", server.DisplayParticipantForm(&logins))
mux.HandleFunc("/internal-login/", session.HandleInternalLogin(db, &sessions, sessionChan))
mux.HandleFunc("/external-login/", session.HandleExternalLogin(&sessions))
go mux.HandleSessions(db, sessionChan, &sessions)
log.Fatalln(http.ListenAndServe(":8080", mux))
}

View File

@ -1,4 +1,10 @@
package types
package data
import "database/sql"
type DB struct {
*sql.DB
}
type Person struct {
ID int64

View File

@ -1,309 +0,0 @@
package data
import (
"bufio"
"database/sql"
"fmt"
"os"
"strings"
"syscall"
"streifling.com/jason/sicherheitsunterweisung/packages/types"
"github.com/go-sql-driver/mysql"
"golang.org/x/term"
)
type DB struct {
*sql.DB
Name string
}
func getUsername() (string, error) {
user := os.Getenv("DB_USER")
if user == "" {
var err error
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
}
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 OpenDB(dbName string) (*DB, error) {
var err error
db := new(DB)
cfg := mysql.NewConfig()
cfg.DBName = dbName
cfg.User, cfg.Passwd, err = getCredentials()
if err != nil {
return nil, fmt.Errorf("Open: getCredentials(): %v\n", err)
}
db.Name = dbName
db.DB, err = sql.Open("mysql", cfg.FormatDSN())
if err != nil {
return nil, fmt.Errorf("Open: sql.Open(\"mysql\", cfg.FormatDSN()): %v\n", err)
}
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("Open: db.Ping(): %v\n", err)
}
return db, nil
}
func (db *DB) WriteBriefing(b *types.Briefing) error {
result, err := db.Exec(`
INSERT INTO briefings
(date, time, location, document_name, as_of, instructor_id)
VALUES
(?, ?, ?, ?, ?, ?)
`, b.Date, b.Time, b.Location, b.DocumentName, b.AsOf, b.InstructorID)
if err != nil {
return fmt.Errorf("*DB.writeBriefing: db.Exec(): %v\n", err)
}
b.ID, err = result.LastInsertId()
if err != nil {
return fmt.Errorf("*DB.writeBriefing: result.LastInsertId(): %v\n", err)
}
return nil
}
func (db *DB) WriteParticipant(p *types.Participant) error {
result, err := db.Exec(`
INSERT INTO participants
(first_name, last_name, company)
VALUES
(?, ?, ?)
`, p.FirstName, p.LastName, p.Company)
if err != nil {
return fmt.Errorf("*DB.writeParticipants: db.Exec(): %v\n", err)
}
p.ID, err = result.LastInsertId()
if err != nil {
return fmt.Errorf("*DB.writeParticipants: result.LastInsertId(): %v\n", err)
}
return nil
}
func (db *DB) WriteGivenAnswer(b *types.Briefing, p *types.Participant, q *types.Question, g *types.GivenAnswer) error {
_, err := db.Exec(`
INSERT INTO given_answers
(briefing_id, participant_id, question_id, given_answer)
VALUES
(?, ?, ?, ?)
`, b.ID, p.ID, q.ID, g)
if err != nil {
return fmt.Errorf("*DB.writeGivenAnswers: db.Exec(): %v\n", err)
}
return nil
}
func (db *DB) WriteAllDataOfBriefing(b *types.Briefing, sp *[]*types.Participant, sq *[]*types.Question, sg *[]*types.GivenAnswer) error {
if err := db.WriteBriefing(b); err != nil {
return fmt.Errorf("*DB.WriteAllDataOfBriefing: db.writeBriefing(): %v\n", err)
}
for _, p := range *sp {
if err := db.WriteParticipant(p); err != nil {
return fmt.Errorf("*DB.WriteAllDataOfBriefing: db.writeParticipants(): %v\n", err)
}
}
for _, p := range *sp {
for i, q := range *sq {
db.WriteGivenAnswer(b, p, q, (*sg)[i])
}
}
return nil
}
func (db *DB) GetAllOverviewTableData() (*[]*types.OverviewTableData, error) {
rows, err := db.Query(`
SELECT
i.first_name,
i.last_name,
b.date,
b.time,
b.location,
b.document_name,
b.as_of,
p.first_name,
p.last_name,
p.company
FROM given_answers AS g
INNER JOIN briefings AS b
ON b.id = g.briefing_id
INNER JOIN participants AS p
ON p.id = g.participant_id
INNER JOIN instructors AS i
ON i.id = b.instructor_id
ORDER BY
b.id DESC,
p.id
`)
if err != nil {
return nil, fmt.Errorf("*DB.ReadAllBriefings: db.Query(): %v\n", err)
}
defer rows.Close()
data := make([]*types.OverviewTableData, 0)
for rows.Next() {
otd := new(types.OverviewTableData)
err := rows.Scan(
&otd.InstructorFirstName,
&otd.InstructorLastName,
&otd.BriefingDate,
&otd.BriefingTime,
&otd.BriefingLocation,
&otd.BriefingDocumentName,
&otd.BriefingAsOf,
&otd.ParticipantFirstName,
&otd.ParticipantLastName,
&otd.ParticipantCompany,
)
if err != nil {
return nil, fmt.Errorf("*DB.ReadAllBriefings: rows.Scan(): %v\n", err)
}
data = append(data, otd)
}
return &data, nil
}
func (db *DB) GetOverviewTableDataByName(n string) (*[]*types.OverviewTableData, error) {
rows, err := db.Query(`
SELECT
i.first_name,
i.last_name,
b.date,
b.time,
b.location,
b.document_name,
b.as_of,
p.first_name,
p.last_name,
p.company
FROM given_answers AS g
INNER JOIN briefings AS b
ON b.id = g.briefing_id
INNER JOIN participants AS p
ON p.id = g.participant_id
INNER JOIN instructors AS i
ON i.id = b.instructor_id
WHERE
i.first_name LIKE ? OR
i.last_name LIKE ? OR
p.first_name LIKE ? OR
p.last_name LIKE ?
ORDER BY
b.id DESC,
p.id
`, "%"+n+"%", "%"+n+"%", "%"+n+"%", "%"+n+"%")
if err != nil {
return nil, fmt.Errorf("*DB.GetOverviewTableDataByName: db.Query(): %v\n", err)
}
defer rows.Close()
data := make([]*types.OverviewTableData, 0)
for rows.Next() {
otd := new(types.OverviewTableData)
err := rows.Scan(
&otd.InstructorFirstName,
&otd.InstructorLastName,
&otd.BriefingDate,
&otd.BriefingTime,
&otd.BriefingLocation,
&otd.BriefingDocumentName,
&otd.BriefingAsOf,
&otd.ParticipantFirstName,
&otd.ParticipantLastName,
&otd.ParticipantCompany,
)
if err != nil {
return nil, fmt.Errorf("*DB.ReadAllBriefings: rows.Scan(): %v\n", err)
}
data = append(data, otd)
}
return &data, nil
}
func (db *DB) GetLastID(table string) (int, error) {
var id int
row := db.QueryRow(`
SELECT id
FROM ?
ORDER BY id DESC
LIMIT 0, 1
`, table)
if err := row.Scan(&id); err != nil {
return -1, fmt.Errorf("*DB.GetLastID: row.Scan(): %v\n", err)
}
return id, nil
}
func (db *DB) GetInstructors() (*[]*types.Instructor, error) {
rows, err := db.Query(`
SELECT *
FROM instructors
`)
if err != nil {
return nil, fmt.Errorf("*DB.GetInstructors: db.Query(): %v\n", err)
}
instructors := make([]*types.Instructor, 0)
for rows.Next() {
instructor := new(types.Instructor)
if err = rows.Scan(instructor); err != nil {
return nil, fmt.Errorf("*DB.GetInstructors: rows.Scan(): %v\n", err)
}
instructors = append(instructors, instructor)
}
return &instructors, nil
}

326
packages/data/dbFuncs.go Normal file
View File

@ -0,0 +1,326 @@
package data
import (
"database/sql"
"fmt"
"strings"
"github.com/go-sql-driver/mysql"
)
func OpenDB(dbName string) (*DB, error) {
var err error
db := new(DB)
cfg := mysql.NewConfig()
cfg.DBName = dbName
cfg.User, cfg.Passwd, err = getCredentials()
if err != nil {
return nil, fmt.Errorf("error: OpenDB: getCredentials(): %v", err)
}
db.DB, err = sql.Open("mysql", cfg.FormatDSN())
if err != nil {
return nil, fmt.Errorf("error: OpenDB: sql.Open(\"mysql\", cfg.FormatDSN()): %v", err)
}
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("error: OpenDB: db.Ping(): %v", err)
}
return db, nil
}
func (db *DB) WriteBriefing(b *Briefing) error {
query := `
INSERT INTO briefings
(date, time, location, document_name, as_of, instructor_id)
VALUES
(?, ?, ?, ?, ?, ?)
`
result, err := db.Exec(query, b.Date, b.Time, b.Location, b.DocumentName, b.AsOf, b.InstructorID)
if err != nil {
return fmt.Errorf("error: *DB.writeBriefing: db.Exec(): %v", err)
}
b.ID, err = result.LastInsertId()
if err != nil {
return fmt.Errorf("error: *DB.writeBriefing: result.LastInsertId(): %v", err)
}
return nil
}
func (db *DB) WriteParticipant(p *Participant) error {
query := `
INSERT INTO participants
(first_name, last_name, company)
VALUES
(?, ?, ?)
`
result, err := db.Exec(query, p.FirstName, p.LastName, p.Company)
if err != nil {
return fmt.Errorf("error: *DB.writeParticipants: db.Exec(): %v", err)
}
p.ID, err = result.LastInsertId()
if err != nil {
return fmt.Errorf("error: *DB.writeParticipants: result.LastInsertId(): %v", err)
}
return nil
}
func (db *DB) WriteGivenAnswers(b Briefing, p Participant, sq []Question, givenAnswers []int) error {
query := `
INSERT INTO given_answers
(briefing_id, participant_id, question_id, given_answer)
VALUES
(?, ?, ?, ?)
`
for i, q := range sq {
_, err := db.Exec(query, b.ID, p.ID, q.ID, givenAnswers[i])
if err != nil {
return fmt.Errorf("error: *DB.WriteGivenAnswers: db.Exec(): %v", err)
}
}
return nil
}
func (db *DB) GetAllOverviewTableData() ([]OverviewTableData, error) {
query := `
SELECT
i.first_name,
i.last_name,
b.date,
b.time,
b.location,
b.document_name,
b.as_of,
p.first_name,
p.last_name,
p.company
FROM given_answers AS g
INNER JOIN briefings AS b
ON b.id = g.briefing_id
INNER JOIN participants AS p
ON p.id = g.participant_id
INNER JOIN questions AS q
ON q.id = g.question_id
INNER JOIN instructors AS i
ON i.id = b.instructor_id
WHERE
q.id = 1
ORDER BY
b.id DESC,
p.id
`
rows, err := db.Query(query)
if err != nil {
return nil, fmt.Errorf("error: *DB.ReadAllBriefings: db.Query(): %v", err)
}
defer rows.Close()
data := make([]OverviewTableData, 0)
for rows.Next() {
otd := new(OverviewTableData)
err := rows.Scan(
&otd.InstructorFirstName,
&otd.InstructorLastName,
&otd.BriefingDate,
&otd.BriefingTime,
&otd.BriefingLocation,
&otd.BriefingDocumentName,
&otd.BriefingAsOf,
&otd.ParticipantFirstName,
&otd.ParticipantLastName,
&otd.ParticipantCompany,
)
if err != nil {
return nil, fmt.Errorf("error: *DB.ReadAllBriefings: rows.Scan(): %v", err)
}
data = append(data, *otd)
}
return data, nil
}
func (db *DB) GetOverviewTableDataByName(n string) ([]OverviewTableData, error) {
query := `
SELECT
i.first_name,
i.last_name,
b.date,
b.time,
b.location,
b.document_name,
b.as_of,
p.first_name,
p.last_name,
p.company
FROM given_answers AS g
INNER JOIN briefings AS b
ON b.id = g.briefing_id
INNER JOIN participants AS p
ON p.id = g.participant_id
INNER JOIN instructors AS i
ON i.id = b.instructor_id
WHERE
q.id = 1 AND
i.first_name LIKE ? OR
i.last_name LIKE ? OR
p.first_name LIKE ? OR
p.last_name LIKE ?
ORDER BY
b.id DESC,
p.id
`
rows, err := db.Query(query, "%"+n+"%", "%"+n+"%", "%"+n+"%", "%"+n+"%")
if err != nil {
return nil, fmt.Errorf("error: *DB.GetOverviewTableDataByName: db.Query(): %v", err)
}
defer rows.Close()
data := make([]OverviewTableData, 0)
for rows.Next() {
otd := new(OverviewTableData)
err := rows.Scan(
&otd.InstructorFirstName,
&otd.InstructorLastName,
&otd.BriefingDate,
&otd.BriefingTime,
&otd.BriefingLocation,
&otd.BriefingDocumentName,
&otd.BriefingAsOf,
&otd.ParticipantFirstName,
&otd.ParticipantLastName,
&otd.ParticipantCompany,
)
if err != nil {
return nil, fmt.Errorf("error: *DB.ReadAllBriefings: rows.Scan(): %v", err)
}
data = append(data, *otd)
}
return data, nil
}
func (db *DB) GetLastID(table string) (int, error) {
var id int
query := `
SELECT id
FROM ?
ORDER BY id DESC
LIMIT 0, 1
`
row := db.QueryRow(query, table)
if err := row.Scan(&id); err != nil {
return -1, fmt.Errorf("error: *DB.GetLastID: row.Scan(): %v", err)
}
return id, nil
}
func (db *DB) GetInstructors() ([]*Instructor, error) {
query := `
SELECT *
FROM instructors
ORDER BY
last_name,
first_name
`
rows, err := db.Query(query)
if err != nil {
return nil, fmt.Errorf("error: *DB.GetInstructors: db.Query(): %v", err)
}
defer rows.Close()
instructors := make([]*Instructor, 0)
for rows.Next() {
instructor := new(Instructor)
if err = rows.Scan(&instructor.ID, &instructor.FirstName, &instructor.LastName); err != nil {
return nil, fmt.Errorf("error: *DB.GetInstructors: rows.Scan(): %v", err)
}
instructors = append(instructors, instructor)
}
return instructors, nil
}
func (db *DB) GetQuestions(nums []string) ([]Question, error) {
query := `
SELECT *
FROM questions
WHERE id IN (` + strings.Join(nums, ", ") + `)
`
rows, err := db.Query(query)
if err != nil {
return nil, fmt.Errorf("error: *DB.GetQuestions: db.Query(): %v", err)
}
defer rows.Close()
// TODO: not scalable
questions := make([]Question, 0)
for rows.Next() {
q := new(Question)
a1 := new(Answer)
a2 := new(Answer)
a3 := new(Answer)
a4 := new(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("error: *DB.GetQuestions: rows.Scan(): %v", 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 []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("error: *DB.GetGivenAnswers: row.Scan(): %v", err)
}
answers = append(answers, answer)
}
return answers, nil
}

View File

@ -0,0 +1,52 @@
package data
import (
"bufio"
"fmt"
"os"
"strings"
"syscall"
"golang.org/x/term"
)
func getUsername() (string, error) {
user := os.Getenv("DB_USER")
if user == "" {
var err error
fmt.Printf("DB Benutzer: ")
user, err = bufio.NewReader(os.Stdin).ReadString('\n')
if err != nil {
return "", fmt.Errorf("error: getUsername: bufio.NewReader().ReadString(): %v", err)
}
}
return strings.TrimSpace(user), nil
}
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("error: getPassword: term.ReadPassword(): %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("error: getCredentials: getUsername(): %v", err)
}
pass, err := getPassword()
if err != nil {
return "", "", fmt.Errorf("error: getCredentials: getPassword(): %v", err)
}
return user, pass, nil
}

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,193 +0,0 @@
package server
import (
"crypto/rand"
"encoding/hex"
"fmt"
"html/template"
"net/http"
"strconv"
"time"
"streifling.com/jason/sicherheitsunterweisung/packages/data"
"streifling.com/jason/sicherheitsunterweisung/packages/types"
)
// 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) {
bs, err := db.GetAllOverviewTableData()
if err != nil {
http.Error(w, "DisplayTable: *DB.GetAllOverviewTableData(): "+fmt.Sprint(err), http.StatusInternalServerError)
}
template.Must(template.ParseFiles("templates/table.html")).ExecuteTemplate(w, "content", bs)
}
}
func DisplaySearchResults(db *data.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
bs, err := db.GetOverviewTableDataByName(r.PostFormValue("search"))
if err != nil {
http.Error(w, "DisplayResults: db.ReadByName(r.PostFormValue()): "+fmt.Sprint(err), http.StatusInternalServerError)
}
template.Must(template.ParseFiles("templates/table.html")).ExecuteTemplate(w, "rows", bs)
}
}
func DisplayInstructorForm() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "content", nil)
}
}
func generateUUID() (string, error) {
bs := make([]byte, 4)
if _, err := rand.Read(bs); err != nil {
return "", fmt.Errorf("GenerateUUID: rand.Read(bs): %v\n", err)
}
return hex.EncodeToString(bs), nil
}
func AddParticipant(sl *[]string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
login, err := generateUUID()
if err != nil {
http.Error(w, "AddParticipant: generateUUID(): "+fmt.Sprint(err), http.StatusInternalServerError)
}
(*sl) = append(*sl, login)
template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "new", login)
}
}
// TODO: Hier weiter machen, irgendwie die b.ID herausgeben,
// 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) {
now := time.Now()
briefing := new(types.Briefing)
// TODO: Dropdownmenü
// instructorFirstName := r.PostFormValue("instructor-first")
// instructorLastName := r.PostFormValue("instructor-last")
briefing.Date = now.Format("2006-01-02")
briefing.Time = now.Format("15:04:05")
briefing.Location = r.PostFormValue("location")
briefing.DocumentName = r.PostFormValue("document") // TODO: in HTML einfügen
briefing.AsOf = r.PostFormValue("state") // TODO: Umbenennen
// briefing.InstructorID = r.PostFormValue("instructor-id") // TODO: aus Dropdown holen
db.WriteBriefing(briefing)
}
}
// 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) {
var err error
p := new(types.Participant)
p.ID, err = strconv.ParseInt(l, 10, 64)
if err != nil {
return nil, fmt.Errorf("newParticipant: strconv.Atoi(idString): %v\n", err)
}
return p, nil
}
func DisplayParticipantForm(sl *[]string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if loginIsCorrect(r.PostFormValue("login"), sl) {
uuid, err := generateUUID()
if err != nil {
http.Error(w, "DisplayParticipantForm: generateUUID(): "+fmt.Sprint(err), http.StatusInternalServerError)
}
template.Must(template.ParseFiles("templates/participant.html")).ExecuteTemplate(w, "content", uuid)
} 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(): %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

@ -0,0 +1,254 @@
package session
import (
"fmt"
"html/template"
"log"
"net/http"
"time"
"github.com/google/uuid"
"streifling.com/jason/sicherheitsunterweisung/packages/data"
)
func HandleInternalLogin(db *data.DB, ss *[]*Session, cs chan<- *Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
instructors, err := db.GetInstructors()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Panicln(err)
}
for _, i := range instructors {
if r.PostFormValue("login") == fmt.Sprint(i.ID) {
session := new(Session)
session.ID = uuid.New()
session.Briefing = new(data.Briefing)
session.InstructorID = i.ID
(*ss) = append((*ss), session)
cs <- session
data := new(tableHTMLData)
data.SessionID = session.ID
data.OTD, err = db.GetAllOverviewTableData()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Panicln(err)
}
template.Must(template.ParseFiles("templates/table.html")).ExecuteTemplate(w, "content", data)
return
}
}
template.Must(template.ParseFiles("templates/login.html")).ExecuteTemplate(w, "content", nil)
}
}
func (s *Session) HandleSearch(db *data.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println("hier")
var err error
data := tableHTMLData{}
data.SessionID = s.ID
data.OTD, err = db.GetOverviewTableDataByName(r.PostFormValue("search"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Panicln(err)
}
template.Must(template.ParseFiles("templates/table.html")).ExecuteTemplate(w, "rows", data)
}
}
func (s *Session) HandleNewBriefing() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data := new(participantHTMLData)
data.SessionID = s.ID
template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "content", data)
}
}
func (s *Session) HandleNewParticipant(cp chan<- *BriefingParticipant) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var err error
p := new(BriefingParticipant)
p.Participant = new(data.Participant)
p.NoIncorrect = -1
p.AllowRetry = false
p.Login, err = generateLogin()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Panicln(err)
}
s.Participants = append(s.Participants, p)
cp <- p
data := new(participantHTMLData)
data.SessionID = s.ID
data.Login = p.Login
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Panicln(err)
}
template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "new", data)
}
}
func (s *Session) HandleBriefingForm(db *data.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
now := time.Now()
s.Date = now.Format("2006-01-02")
s.Time = now.Format("15:04:05")
s.Location = r.PostFormValue("location")
s.DocumentName = r.PostFormValue("document")
s.AsOf = r.PostFormValue("as-of")
err := db.WriteBriefing(s.Briefing)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Panicln(err)
}
data := new(summaryHTMLData)
data.SessionID = s.ID
data.ParticipantsData = make([]participantHTMLData, len(s.Participants))
for i, p := range s.Participants {
data.ParticipantsData[i].SessionID = s.ID
data.ParticipantsData[i].BriefingParticipant = *p
}
template.Must(template.ParseFiles("templates/summary.html")).ExecuteTemplate(w, "content", data)
}
}
func HandleExternalLogin(ss *[]*Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, participant, loginCorrect := findCorrectLogin(r.PostFormValue("login"), ss)
if loginCorrect {
data := new(participantHTMLData)
data.SessionID = session.ID
data.Login = participant.Login
template.Must(template.ParseFiles("templates/participant.html")).ExecuteTemplate(w, "content", data)
} else {
template.Must(template.ParseFiles("templates/login.html")).ExecuteTemplate(w, "content", nil)
}
}
}
func (s *Session) HandleParticipant(db *data.DB, p *BriefingParticipant) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
p.FirstName = r.PostFormValue("first-" + fmt.Sprint(p.Login))
p.LastName = r.PostFormValue("last-" + fmt.Sprint(p.Login))
p.Company = r.PostFormValue("company-" + fmt.Sprint(p.Login))
err := db.WriteParticipant(p.Participant)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Panicln(err)
}
data := new(questionHTMLData)
data.SessionID = s.ID
data.Login = p.Login
data.Question = s.Questions[0]
data.QuestionID = 1
template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
}
}
func (s *Session) HandleAnswer(db *data.DB, p *BriefingParticipant, i int64) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if i < int64(len(s.Questions)) {
if err := handleGivenAnswer(p, i-1, r); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Panicln(err)
}
data := new(questionHTMLData)
data.SessionID = s.ID
data.Login = p.Login
data.Question = s.Questions[i]
data.QuestionID = i + 1
template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
} else {
if err := handleGivenAnswer(p, i-1, r); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Panicln(err)
}
p.NoIncorrect = 0
for i, q := range s.Questions {
if p.GivenAnswers[i] != q.Correct {
p.NoIncorrect++
}
}
data := new(resultHTMLData)
data.SessionID = s.ID
data.BriefingParticipant = *p
data.Questions = makeHTMLQuestions(s.Questions, p.GivenAnswers)
if data.NoIncorrect == 0 {
if err := db.WriteGivenAnswers(*s.Briefing, *p.Participant, s.Questions, p.GivenAnswers); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Panicln(err)
}
}
template.Must(template.ParseFiles("templates/result.html")).ExecuteTemplate(w, "content", data)
}
}
}
func (s *Session) HandleAllowRetry(p *BriefingParticipant) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
p.NoIncorrect = -1
p.AllowRetry = true
}
}
func (s *Session) HandleRetry(p *BriefingParticipant, i *int) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if p.AllowRetry {
p.AllowRetry = false
(*i) = 0
data := new(questionHTMLData)
data.SessionID = s.ID
data.Login = p.Login
data.Question = s.Questions[*i]
data.QuestionID = int64(*i + 1)
template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
} else {
data := new(resultHTMLData)
data.SessionID = s.ID
data.BriefingParticipant = *p
data.Questions = makeHTMLQuestions(s.Questions, p.GivenAnswers)
template.Must(template.ParseFiles("templates/result.html")).ExecuteTemplate(w, "content", data)
}
}
}
func (s *Session) HandleRefresh(p *BriefingParticipant) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data := new(participantHTMLData)
data.SessionID = s.ID
data.BriefingParticipant = *p
template.Must(template.ParseFiles("templates/summary.html")).ExecuteTemplate(w, "participant", data)
}
}
func (s *Session) HandleBriefingDone() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
template.Must(template.ParseFiles("templates/login.html")).ExecuteTemplate(w, "content", nil)
}
}

View File

@ -0,0 +1,84 @@
package session
import (
"crypto/rand"
"encoding/hex"
"fmt"
"net/http"
"strconv"
"streifling.com/jason/sicherheitsunterweisung/packages/data"
)
func generateLogin() (string, error) {
bs := make([]byte, 4)
if _, err := rand.Read(bs); err != nil {
return "", fmt.Errorf("error: generateLogin: rand.Read(bs): %v", err)
}
return hex.EncodeToString(bs), nil
}
func findCorrectLogin(l string, ss *[]*Session) (*Session, *BriefingParticipant, bool) {
for _, session := range *ss {
for _, p := range session.Participants {
if l == p.Login {
return session, p, true
}
}
}
return nil, nil, false
}
func newParticipant(l string) (*data.Participant, error) {
var err error
p := new(data.Participant)
p.ID, err = strconv.ParseInt(l, 10, 64)
if err != nil {
return nil, fmt.Errorf("error: newParticipant: strconv.Atoi(): %v", err)
}
return p, nil
}
func handleGivenAnswer(p *BriefingParticipant, i int64, r *http.Request) error {
answer, err := strconv.Atoi(r.PostFormValue("answer"))
if err != nil {
return fmt.Errorf("error: handleGivenAnswer: strconv.Atoi(): %v", err)
}
p.GivenAnswers[i] = answer
return nil
}
func makeHTMLQuestions(sq []data.Question, givenAnswers []int) []resultQuestion {
questions := make([]resultQuestion, 0)
for i, q := range sq {
question := new(resultQuestion)
question.Text = q.Text
question.Answers = make([]resultAnswer, 0)
for j, a := range q.Answers {
answer := new(resultAnswer)
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)
}
questions = append(questions, *question)
}
return questions
}

View File

@ -0,0 +1,43 @@
package session
import (
"github.com/google/uuid"
"streifling.com/jason/sicherheitsunterweisung/packages/data"
)
type tableHTMLData struct {
SessionID uuid.UUID
OTD []data.OverviewTableData
}
type participantHTMLData struct {
SessionID uuid.UUID
BriefingParticipant
}
type questionHTMLData struct {
participantHTMLData
Question data.Question
QuestionID int64
}
type resultAnswer struct {
Text string
Correct bool
Chosen bool
}
type resultQuestion struct {
Text string
Answers []resultAnswer
}
type resultHTMLData struct {
participantHTMLData
Questions []resultQuestion
}
type summaryHTMLData struct {
SessionID uuid.UUID
ParticipantsData []participantHTMLData
}

View File

@ -0,0 +1,56 @@
package session
import (
"fmt"
"log"
"net/http"
"streifling.com/jason/sicherheitsunterweisung/packages/data"
)
func (mux *Mux) handleParticipants(db *data.DB, cp <-chan *BriefingParticipant, s *Session) {
for p := range cp {
p.GivenAnswers = make([]int, len(s.Questions))
mux.HandleFunc("/submit-participant/"+fmt.Sprint(s.ID)+"/"+fmt.Sprint(p.Login)+"/", s.HandleParticipant(db, p))
var i int
for i = range s.Questions {
mux.HandleFunc("/submit-answer/"+fmt.Sprint(s.ID)+"/"+fmt.Sprint(p.Login)+"/"+fmt.Sprint(i+1)+"/", s.HandleAnswer(db, p, int64(i+1)))
}
mux.HandleFunc("/allow-retry/"+fmt.Sprint(s.ID)+"/"+fmt.Sprint(p.Login)+"/", s.HandleAllowRetry(p))
mux.HandleFunc("/retry/"+fmt.Sprint(s.ID)+"/"+fmt.Sprint(p.Login)+"/", s.HandleRetry(p, &i))
mux.HandleFunc("/refresh-summary/"+fmt.Sprint(s.ID)+"/"+fmt.Sprint(p.Login)+"/", s.HandleRefresh(p))
}
}
func NewMux() *Mux {
mux := new(Mux)
mux.ServeMux = http.NewServeMux()
return mux
}
func (mux *Mux) HandleSessions(db *data.DB, cs <-chan *Session, ss *[]*Session) {
for s := range cs {
(*ss) = append(*ss, s)
participantChan := make(chan *BriefingParticipant)
questionIDs := make([]string, 4)
for i := 0; i < len(questionIDs); i++ {
questionIDs[i] = fmt.Sprint(i + 1)
}
var err error
s.Questions, err = db.GetQuestions(questionIDs)
if err != nil {
log.Fatalln(err)
}
mux.HandleFunc("/search/"+fmt.Sprint(s.ID)+"/", s.HandleSearch(db))
mux.HandleFunc("/new-briefing/"+fmt.Sprint(s.ID)+"/", s.HandleNewBriefing())
mux.HandleFunc("/new-participant/"+fmt.Sprint(s.ID)+"/", s.HandleNewParticipant(participantChan))
mux.HandleFunc("/submit-form/"+fmt.Sprint(s.ID)+"/", s.HandleBriefingForm(db))
mux.HandleFunc("/briefing-done/"+fmt.Sprint(s.ID)+"/", s.HandleBriefingDone())
go mux.handleParticipants(db, participantChan, s)
}
}

View File

@ -0,0 +1,27 @@
package session
import (
"net/http"
"github.com/google/uuid"
"streifling.com/jason/sicherheitsunterweisung/packages/data"
)
type Mux struct {
*http.ServeMux
}
type BriefingParticipant struct {
*data.Participant
Login string
GivenAnswers []int
NoIncorrect int
AllowRetry bool
}
type Session struct {
ID uuid.UUID
*data.Briefing
Participants []*BriefingParticipant
Questions []data.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" }}
<button type="button" hx-post="/add-participant/" hx-target="this" hx-swap="outerHTML">
{{define "add-buttons"}}
<div id="briefing-buttons">
<button type="button" hx-post="/new-participant/{{.SessionID}}/" hx-target="#briefing-buttons" hx-swap="outerHTML">
Neuer Teilnehmer
</button>
<button type="submit" hx-post="/submit-form/{{.SessionID}}/" hx-target="#content">
Weiter
</button>
</div>
{{end}}
{{define "new"}}
<span>{{ . }}</span>
{{ template "add-button" . }}
{{template "add-buttons" .}}
<p>{{.Login}}</p>
{{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>
<label for="location">Ort</label>
<input id="location" name="location" required type="text" value="Werk Langenhagen" />
</div>
<div id="location">
<label for="location-input">Ort</label>
<input type="text" name="location" id="location-input" />
<div>
<label for="document">Dokument</label>
<input id="document" name="document" required type="text" value="ICL-1901-LGH" />
</div>
<div id="state">
<label for="state-input">Stand vom</label>
<input type="date" name="state" id="state-input" />
<div>
<label for="as-of">Stand vom</label>
<input id="as-of" name="as-of" required type="date" value="2021-02-01" />
</div>
{{ template "add-button" . }}
<button type="submit" hx-post="/submit-form/" hx-target="#content" hx-swap="innerHTML">
OK
</button>
{{template "add-buttons" .}}
</form>
{{end}}

View File

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

View File

@ -1,15 +1,15 @@
{{define "content"}}
<form id="participant-{{ . }}">
<label for="participant-first-input-{{ . }}">Vorname</label>
<input type="text" name="participant-first-{{ . }}" id="participant-first-input-{{ . }}" />
<form>
<label for="first-{{.Login}}">Vorname</label>
<input type="text" name="first-{{.Login}}" id="first-{{.Login}}" />
<label for="participant-last-input-{{ . }}">Nachname</label>
<input type="text" name="participant-last-{{ . }}" id="participant-last-input-{{ . }}" />
<label for="last-{{.Login}}">Nachname</label>
<input type="text" name="last-{{.Login}}" id="last-{{.Login}}" />
<label for="participant-company-input-{{ . }}">Firma</label>
<input type="text" name="participant-company-{{ . }}" id="participant-company-input-{{ . }}" />
<label for="company-{{.Login}}">Firma</label>
<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
</button>
</form>

View File

@ -1,5 +1,5 @@
{{define "answers"}}
{{ range .Q.Answers }}
{{range .Question.Answers}}
<div>
<input type="radio" name="answer" id="answer-{{.ID}}" value="{{.ID}}" />
<label for="answer-{{.ID}}">{{.Text}}</label>
@ -8,13 +8,13 @@
{{end}}
{{define "content"}}
<h2>Frage {{ .I }}</h2>
<p>{{ .Q.Text }}</p>
<h2>Frage {{.QuestionID}}</h2>
<p>{{.Question.Text}}</p>
<form>
{{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
</button>
</form>

19
templates/result.html Normal file
View File

@ -0,0 +1,19 @@
{{define "answers"}}
{{range .Answers}}
<p class="{{if and .Chosen .Correct}} correct {{else if and .Chosen (not .Correct)}} incorrect {{end}}">
{{.Text}}
</p>
{{end}}
{{end}}
{{define "content"}}
<p>{{.BriefingParticipant.NoIncorrect}} Fehler</p>
{{if gt .BriefingParticipant.NoIncorrect 0}}
<p>Bitte nachschulen lassen und anschließend wiederholen.</p>
<button hx-post="/retry/{{.SessionID}}/{{.Login}}/" hx-target="#content" type="submit">Wiederholen</button>
{{end}}
{{range .Questions}}
<p>{{.Text}}</p>
{{template "answers" .}}
{{end}}
{{end}}

View File

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

35
templates/summary.html Normal file
View File

@ -0,0 +1,35 @@
{{define "refresh"}}
<button hx-post="/refresh-summary/{{.SessionID}}/{{.Login}}/" hx-target="#id-{{.Login}}"
type="button">Aktualisieren</button>
{{end}}
{{define "retry"}}
<button hx-post="/allow-retry/{{.SessionID}}/{{.Login}}/" type="button">Wiederholen erlauben</button>
{{end}}
{{define "participant"}}
<div id="id-{{.Login}}">
<h2>{{.Login}}</h2>
{{if not .LastName}}
{{template "refresh" .}}
{{else}}
<p>{{.FirstName}} {{.LastName}}</p>
<p>{{.Company}}</p>
{{if lt .NoIncorrect 0}}
{{template "refresh" .}}
{{else if gt .NoIncorrect 0}}
{{template "retry" .}}
{{template "refresh" .}}
{{else}}
<p>{{.NoIncorrect}} Fehler</p>
{{end}}
{{end}}
</div>
{{end}}
{{define "content"}}
<button hx-post="/briefing-done/{{.SessionID}}/" hx-target="#content" type="button">Beenden</button>
{{range .ParticipantsData}}
{{template "participant" .}}
{{end}}
{{end}}

View File

@ -1,17 +1,16 @@
{{define "rows"}}
{{ range . }}
{{range .OTD}}
<tr>
<td>{{ .FirstName }}</td>
<td>{{ .LastName }}</td>
<td>{{ .Date }}</td>
<td>{{ .Time }}</td>
<td>{{ .State }}</td>
<td>{{ .Location }}</td>
{{ range .Participants }}
<td>{{ .FirstName }}</td>
<td>{{ .LastName }}</td>
<td>{{ .Company }}</td>
{{ end }}
<td>{{.InstructorFirstName}}</td>
<td>{{.InstructorLastName}}</td>
<td>{{.BriefingDate}}</td>
<td>{{.BriefingTime}}</td>
<td>{{.BriefingLocation}}</td>
<td>{{.BriefingDocumentName}}</td>
<td>{{.BriefingAsOf}}</td>
<td>{{.ParticipantFirstName}}</td>
<td>{{.ParticipantLastName}}</td>
<td>{{.ParticipantCompany}}</td>
</tr>
{{end}}
{{end}}
@ -19,12 +18,11 @@
{{define "content"}}
<form>
<label for="search-input">Suche</label>
<input type="text" name="search" id="search-input" hx-post="/search/" hx-target="#results" hx-swap="innerHTML"
hx-trigger="keyup changed delay:200ms" />
<input type="text" name="search" id="search-input" hx-post="/search/{{.SessionID}}/" hx-target="#results">
</form>
<form>
<button type="submit" hx-post="/new-briefing/" hx-target="#content" hx-swap="innerHTML">
<button type="submit" hx-post="/new-briefing/{{.SessionID}}/" hx-target="#content">
Neue Unterweisung
</button>
</form>
@ -35,8 +33,9 @@
<th colspan="2">Unterweiser</th>
<th>Datum</th>
<th>Uhrzeit</th>
<th>Stand</th>
<th>Ort</th>
<th>Dokument</th>
<th>Stand</th>
<th colspan="2">Teilnehmer</th>
<th>Firma</th>
</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.