Compare commits
23 Commits
datenbank_
...
7144489afb
Author | SHA1 | Date | |
---|---|---|---|
7144489afb | |||
76f1fe9588 | |||
726c8b6dcb | |||
608879d008 | |||
616df72041 | |||
1597d38d34 | |||
8dbb5f946d | |||
7c7cb5959d | |||
2beb90a345 | |||
f80dca4b10 | |||
bf05bc0be7 | |||
f6a073fc39 | |||
82870e100f | |||
2b119f6752 | |||
c04932383e | |||
8ea0c2964a | |||
519dc82023 | |||
324a1c54d6 | |||
15675d5e6c | |||
8ae3019b9c | |||
b13eba8008 | |||
da77201a93 | |||
71bf8978ac |
44
.air.toml
Normal file
44
.air.toml
Normal 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
1
.gitignore
vendored
@ -1 +0,0 @@
|
||||
create_tables.sql
|
||||
|
96
create_tables.sql
Normal file
96
create_tables.sql
Normal 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' );
|
50
main.go
50
main.go
@ -1,38 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"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/types"
|
||||
)
|
||||
|
||||
func writeBriefing(ch chan *types.Briefing, db *db.DB) {
|
||||
for b := range ch {
|
||||
db.WriteBriefing(b)
|
||||
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() {
|
||||
var i, j int64
|
||||
|
||||
i, j = 1, 1
|
||||
logins := make([]string, 0)
|
||||
participants := make([]*types.Participant, 0)
|
||||
briefings := make([]*types.Briefing, 0)
|
||||
mux := http.NewServeMux()
|
||||
cb := make(chan *types.Briefing)
|
||||
|
||||
db, err := db.Open("sicherheitsunterweisung")
|
||||
db, err := data.OpenDB("sicherheitsunterweisung")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static/"))))
|
||||
mux.HandleFunc("/", server.DisplayForm(&i))
|
||||
mux.HandleFunc("/add-participant/", server.AddParticipant(&i))
|
||||
mux.HandleFunc("/submit/", server.SubmitForm(cb, &i, &j))
|
||||
var i, j int64
|
||||
if err := db.GetLastID(&i); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
j = i
|
||||
|
||||
go writeBriefing(cb, db)
|
||||
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.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.DisplayForm(&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))
|
||||
}
|
||||
|
192
packages/data/db.go
Normal file
192
packages/data/db.go
Normal file
@ -0,0 +1,192 @@
|
||||
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 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
|
||||
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 {
|
||||
for i := 0; i < len(b.Participants); i++ {
|
||||
result, err := db.Exec("INSERT INTO "+db.Name+" (instructor_first,"+
|
||||
" instructor_last, date, time, state, location, participant_first,"+
|
||||
" participant_last, company) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
b.FirstName, b.LastName, b.Date, b.Time, b.State, b.Location,
|
||||
b.Participants[i].FirstName, b.Participants[i].LastName,
|
||||
b.Participants[i].Company)
|
||||
if err != nil {
|
||||
return fmt.Errorf("*DB.WriteBriefing: db.Exec(\"INSERT INTO"+
|
||||
" \"+db.Name+\" (instructor_first, instructor_last, date, time, state,"+
|
||||
" location, participant_first, participant_last, company) VALUES (?, ?,"+
|
||||
" ?, ?, ?, ?, ?, ?, ?)\", b.FirstName, b.LastName, b.Date, b.Time,"+
|
||||
" b.State, b.Location, b.Participants[i].FirstName,"+
|
||||
" b.Participants[i].LastName, b.Participants[i].Company): %v\n", err)
|
||||
}
|
||||
|
||||
_, err = result.LastInsertId()
|
||||
if err != nil {
|
||||
return fmt.Errorf("*DB.WriteBriefing: result.LastInsertId(): %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) ReadAll() ([]*types.Briefing, error) {
|
||||
bs := make([]*types.Briefing, 0)
|
||||
|
||||
rows, err := db.Query("SELECT *" + " FROM " + db.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("*DB.ReadAll: db.Query(\"SELECT * FROM \"+db.Name): %v\n", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
b := new(types.Briefing)
|
||||
p := new(types.Participant)
|
||||
|
||||
if err := rows.Scan(&p.ID, &b.FirstName, &b.LastName, &b.Date, &b.Time,
|
||||
&b.State, &b.Location, &p.FirstName, &p.LastName, &p.Company); err != nil {
|
||||
return nil, fmt.Errorf("*DB.ReadAll: db.Query(): %v\n", err)
|
||||
}
|
||||
|
||||
b.Participants = append(b.Participants, p)
|
||||
bs = append(bs, b)
|
||||
}
|
||||
|
||||
reverseOrder(bs)
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
func (db *DB) ReadByName(name string) ([]*types.Briefing, error) {
|
||||
bs := make([]*types.Briefing, 0)
|
||||
|
||||
rows, err := db.Query("SELECT *"+
|
||||
" FROM "+db.Name+
|
||||
" WHERE instructor_first LIKE ?"+
|
||||
" OR instructor_last LIKE ?"+
|
||||
" OR participant_first LIKE ?"+
|
||||
" OR participant_last LIKE ?",
|
||||
"%"+name+"%", "%"+name+"%", "%"+name+"%", "%"+name+"%")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("*DB.ReadByName: db.Query(\"SELECT *"+
|
||||
" FROM \"+db.Name+"+
|
||||
" WHERE instructor_first LIKE ?"+
|
||||
" OR instructor_last LIKE ?"+
|
||||
" OR participant_first LIKE ?"+
|
||||
" OR participant_last LIKE ?\"): %v\n", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
b := new(types.Briefing)
|
||||
p := new(types.Participant)
|
||||
|
||||
if err := rows.Scan(&p.ID, &b.FirstName, &b.LastName, &b.Date, &b.Time, &b.State,
|
||||
&b.Location, &p.FirstName, &p.LastName, &p.Company); err != nil {
|
||||
return nil, fmt.Errorf("*DB.ReadByName: rows.Scan(&p.ID, &b.FirstName,"+
|
||||
" &b.LastName, &b.Date, &b.Time, &b.State, &b.Location, &p.FirstName,"+
|
||||
" &p.LastName, &p.Company): %v\n", err)
|
||||
}
|
||||
b.Participants = append(b.Participants, p)
|
||||
bs = append(bs, b)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
42
packages/data/questionaires.go
Normal file
42
packages/data/questionaires.go
Normal 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
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
package db
|
||||
|
||||
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 getCredentials() (string, string, error) {
|
||||
fmt.Printf("DB Benutzer: ")
|
||||
user, err := bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("getCredentials: bufio.NewReader(os.Stdin).ReadString('\n'): %v", err)
|
||||
}
|
||||
|
||||
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) {
|
||||
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 {
|
||||
for i := 0; i < len(b.Participants); i++ {
|
||||
result, err := db.Exec("INSERT INTO "+db.Name+" (instructor_first,"+
|
||||
"instructor_last, date, time, state, location, participant_first,"+
|
||||
"participant_last, company) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
b.FirstName, b.LastName, b.Date, b.Time, b.State, b.Location,
|
||||
b.Participants[i].FirstName, b.Participants[i].LastName,
|
||||
b.Participants[i].Company)
|
||||
if err != nil {
|
||||
return fmt.Errorf("*DB.WriteBriefing: db.Exec(\"INSERT INTO"+
|
||||
"\"+db.Name+\" (instructor_first, instructor_last, date, time, state,"+
|
||||
"location, participant_first, participant_last, company) VALUES (?, ?,"+
|
||||
"?, ?, ?, ?, ?, ?, ?)\", b.FirstName, b.LastName, b.Date, b.Time,"+
|
||||
"b.State, b.Location, b.Participants[i].FirstName,"+
|
||||
"b.Participants[i].LastName, b.Participants[i].Company): %v\n", err)
|
||||
}
|
||||
|
||||
_, err = result.LastInsertId()
|
||||
if err != nil {
|
||||
return fmt.Errorf("*DB.WriteBriefing: result.LastInsertId(): %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) ReadByName(name string) (*[]types.Briefing, error) {
|
||||
bs := make([]types.Briefing, 0)
|
||||
|
||||
rows, err := db.Query("SELECT *"+
|
||||
" FROM "+db.Name+
|
||||
" WHERE instructor_first LIKE ?"+
|
||||
" OR instructor_last LIKE ?"+
|
||||
" OR participant_first LIKE ?"+
|
||||
" OR participant_last LIKE ?",
|
||||
"%"+name+"%", "%"+name+"%", "%"+name+"%", "%"+name+"%")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("*DB.ReadByName: db.Query(\"SELECT *"+
|
||||
" FROM \"+db.Name+"+
|
||||
" WHERE instructor_first LIKE ?"+
|
||||
" OR instructor_last LIKE ?"+
|
||||
" OR participant_first LIKE ?"+
|
||||
" OR participant_last LIKE ?\"): %v\n", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
b := new(types.Briefing)
|
||||
p := new(types.Participant)
|
||||
|
||||
if err := rows.Scan(&p.ID, &b.FirstName, &b.LastName, &b.Date, &b.Time, &b.State,
|
||||
&b.Location, &p.FirstName, &p.LastName, &p.Company); err != nil {
|
||||
return nil, fmt.Errorf("*DB.ReadByName: rows.Scan(&p.ID, &b.FirstName,"+
|
||||
" &b.LastName, &b.Date, &b.Time, &b.State, &b.Location, &p.FirstName,"+
|
||||
" &p.LastName, &p.Company): %v\n", err)
|
||||
}
|
||||
b.Participants = append(b.Participants, *p)
|
||||
bs = append(bs, *b)
|
||||
}
|
||||
|
||||
return &bs, nil
|
||||
}
|
@ -1,27 +1,73 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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.ReadAll()
|
||||
if err != nil {
|
||||
_ = fmt.Errorf("DisplayTable: %v\n", err)
|
||||
}
|
||||
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.ReadByName(r.PostFormValue("search"))
|
||||
if err != nil {
|
||||
_ = fmt.Errorf("DisplayResults: db.ReadByName(r.PostFormValue()): %v\n", err)
|
||||
}
|
||||
template.Must(template.ParseFiles("templates/table.html")).ExecuteTemplate(w, "rows", bs)
|
||||
}
|
||||
}
|
||||
|
||||
func DisplayForm(i *int64) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
template.Must(template.ParseFiles("templates/index.html")).Execute(w, 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) {
|
||||
*i++
|
||||
template.Must(template.ParseFiles("templates/index.html", "templates/participant.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(ch chan<- *types.Briefing, i, j *int64) http.HandlerFunc {
|
||||
func SubmitForm(db *data.DB, i, j *int64) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
b := new(types.Briefing)
|
||||
|
||||
@ -33,7 +79,7 @@ func SubmitForm(ch chan<- *types.Briefing, i, j *int64) http.HandlerFunc {
|
||||
b.Location = r.PostFormValue("location")
|
||||
|
||||
for ; *j <= *i; *j++ {
|
||||
b.Participants = append(b.Participants, types.Participant{
|
||||
b.Participants = append(b.Participants, &types.Participant{
|
||||
ID: *j,
|
||||
Person: types.Person{
|
||||
FirstName: r.PostFormValue("participant-first-" + fmt.Sprint(*j)),
|
||||
@ -44,6 +90,117 @@ func SubmitForm(ch chan<- *types.Briefing, i, j *int64) http.HandlerFunc {
|
||||
}
|
||||
|
||||
log.Println(b)
|
||||
ch <- b
|
||||
db.WriteBriefing(b)
|
||||
bs, err := db.ReadAll()
|
||||
if err != nil {
|
||||
_ = fmt.Errorf("SubmitForm: db.ReadAll(): %v\n", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,23 @@ type Person struct {
|
||||
|
||||
type Instructor Person
|
||||
|
||||
type Answer struct {
|
||||
ID int
|
||||
Text string
|
||||
}
|
||||
|
||||
type Question struct {
|
||||
Text string
|
||||
Answers []Answer
|
||||
Chosen int
|
||||
Correct int
|
||||
}
|
||||
|
||||
type Participant struct {
|
||||
ID int64
|
||||
Person
|
||||
Company string
|
||||
Company string
|
||||
Questions []Question
|
||||
}
|
||||
|
||||
type Briefing struct {
|
||||
@ -19,5 +32,5 @@ type Briefing struct {
|
||||
Time string
|
||||
State string
|
||||
Location string
|
||||
Participants []Participant
|
||||
Participants []*Participant
|
||||
}
|
||||
|
38
templates/briefing.html
Normal file
38
templates/briefing.html
Normal 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 }}
|
@ -11,57 +11,9 @@
|
||||
<body>
|
||||
<h1>Sicherheitsunterweisung</h1>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<button type="submit" hx-post="/submit/">
|
||||
Senden
|
||||
</button>
|
||||
</form>
|
||||
<div id="content">
|
||||
{{ template "content" . }}
|
||||
</div>
|
||||
|
||||
<script src="/static/js/htmx.min.js" type="text/javascript"></script>
|
||||
</body>
|
||||
|
16
templates/login.html
Normal file
16
templates/login.html
Normal 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 }}
|
@ -1,5 +1,5 @@
|
||||
{{ define "participant" }}
|
||||
<div id="participant-{{ . }}">
|
||||
{{ define "content" }}
|
||||
<form id="participant-{{ . }}">
|
||||
<label for="participant-first-input-{{ . }}">Vorname</label>
|
||||
<input type="text" name="participant-first-{{ . }}" id="participant-first-input-{{ . }}" />
|
||||
|
||||
@ -8,5 +8,9 @@
|
||||
|
||||
<label for="participant-company-input-{{ . }}">Firma</label>
|
||||
<input type="text" name="participant-company-{{ . }}" id="participant-company-input-{{ . }}" />
|
||||
</div>
|
||||
|
||||
<button type="button" hx-post="/display-question-{{ . }}-0/" hx-target="#content">
|
||||
Fertig
|
||||
</button>
|
||||
</form>
|
||||
{{ end }}
|
||||
|
21
templates/question.html
Normal file
21
templates/question.html
Normal 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
11
templates/results.html
Normal file
@ -0,0 +1,11 @@
|
||||
{{ define "passed" }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "failed" }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "content" }}
|
||||
{{ q := range .Participant.Questions }}
|
||||
<p>{{ . }}{{ q.Text }}</p>
|
||||
{{ end }}
|
||||
{{ end }}
|
49
templates/table.html
Normal file
49
templates/table.html
Normal file
@ -0,0 +1,49 @@
|
||||
{{ define "rows" }}
|
||||
{{ range . }}
|
||||
<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 }}
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ 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" />
|
||||
</form>
|
||||
|
||||
<form>
|
||||
<button type="submit" hx-post="/new-briefing/" hx-target="#content" hx-swap="innerHTML">
|
||||
Neue Unterweisung
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Unterweiser</th>
|
||||
<th>Datum</th>
|
||||
<th>Uhrzeit</th>
|
||||
<th>Stand</th>
|
||||
<th>Ort</th>
|
||||
<th colspan="2">Teilnehmer</th>
|
||||
<th>Firma</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody id="results">
|
||||
{{ template "rows" . }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
1
tmp/build-errors.log
Normal file
1
tmp/build-errors.log
Normal 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
|
Reference in New Issue
Block a user