39 Commits

Author SHA1 Message Date
5019432b24 Noch ein Bisschen aufgeräumt 2023-10-19 15:22:45 +02:00
39d8108521 Viel Code erstmal auskommentiert, um der Logik besser folgen zu können + ein paar Änderungen 2023-10-18 17:08:27 +02:00
82ced65513 server.DisplayParticipantForm aufgeräumt 2023-10-18 16:59:02 +02:00
b605217625 db.WriteParticipant erstellt und dazu alle IDs von int auf int64 umgestellt 2023-10-18 16:43:04 +02:00
db070776b1 server.newParticipant() vereinfacht 2023-10-18 16:26:04 +02:00
3e9cfb49eb server.SubmitForm() zu server.SubmitBriefingForm() umbenannt und weitgehend an neues DB-Layout angepasst 2023-10-18 16:22:39 +02:00
b42f739581 DB.GetLastID() funktioniert wieder und DB.GetInstructors() erstellt für Darstellung in Drop-Down-Menü 2023-10-18 16:21:44 +02:00
c22647edd9 An allen wichtigen Stellen fmt.Errorf() durch http.Error() ersetzt 2023-10-17 18:37:31 +02:00
9bdc6e9f43 db.WriteBriefing umbenannt zu db.WriteAllDataOfBriefing und an neue DB-Struktur angepasst mit passenden Helferfunktionen 2023-10-17 18:28:09 +02:00
b17fa1edc7 TODO hinzugefügt, um GetLastID zu streichen 2023-10-17 17:46:01 +02:00
c69bfdfab2 db.ReadByName zu db.GetOverviewTableDataByName umbenannt und an die neue DB-Struktur angepasst 2023-10-17 17:45:26 +02:00
c38d3131c6 db.ReadAllBriefings zu db.GetAllOverviewTableData umbenannt und die Funktion an das neue DB-Layout angepasst 2023-10-17 17:30:39 +02:00
e4d2f9ae3e Typen dem neuen Datenbanklayout angepasst 2023-10-17 17:04:17 +02:00
523fee3ff2 document_name für Tabelle briefings hinzugefügt, um zwischen Landessprachen unterscheiden zu können 2023-10-17 16:39:21 +02:00
230d79c675 db.ReadAll zu db.ReadAllBriefings umbenannt, um neue DB-Struktur besser zu representieren 2023-10-17 16:36:08 +02:00
664c24974b SQL-Abfrage vereinfacht 2023-10-17 16:34:21 +02:00
7144489afb Bessere Beispiele 2023-10-17 05:25:18 +02:00
76f1fe9588 Großer Umbruch der Architektur, halb fertig, zu viele Details zum Aufzählen, in Zukunft wieder kleine Commits! 2023-10-16 18:51:52 +02:00
726c8b6dcb Login für eingeladene Prüflinge implementiert 2023-10-10 20:57:53 +02:00
608879d008 Generierte UUID in einen asynchronen Speicher schreiben 2023-10-10 19:50:19 +02:00
616df72041 package db zu data umbenannt, außerdem db.Open() zu data.OpenDB() 2023-10-10 19:19:17 +02:00
1597d38d34 UUID-Button repariert 2023-10-10 19:18:34 +02:00
8dbb5f946d UUID-Element zum Ersetzen des "Einladen"-Buttons erstellt 2023-10-10 19:08:59 +02:00
7c7cb5959d GenerateUUID zum erstellen einer pseudozufälligen, 8 Stellen langen Hexadezimalzahl 2023-10-10 19:06:02 +02:00
2beb90a345 Button zum Generieren einer UUID hinzugefügt 2023-10-10 18:55:54 +02:00
f80dca4b10 Bei 82870e10 war question.html nicht gespeichert 2023-10-10 18:55:34 +02:00
bf05bc0be7 Der Participant muss nicht das ganze Questionaire kennen, nur die UUID 2023-10-10 18:52:29 +02:00
f6a073fc39 Alle nötigen types für Questionaire eingefügt 2023-10-10 18:50:54 +02:00
82870e100f question.html erstellt 2023-10-10 18:43:48 +02:00
2b119f6752 Leerzeichen in SQL-Abfragen ergänzt 2023-10-08 15:16:47 +02:00
c04932383e Reihenfolge der ausgegebenen Ergebnisse umgedreht 2023-10-08 15:13:22 +02:00
8ea0c2964a Spalten in Ausgabetabelle umbenannt 2023-10-08 15:12:52 +02:00
519dc82023 Automatisches Suchen ohne Submit-Button 2023-10-08 11:01:57 +02:00
324a1c54d6 Grundlegenden Funktionsumfang geschaffen, dafür einiges umstrukturiert 2023-10-08 10:36:16 +02:00
15675d5e6c HTML-Seiten modularer aufgebaut und table.html hinzugefügt 2023-10-07 18:32:33 +02:00
8ae3019b9c *DB.ReadAll() hinzugefügt 2023-10-07 17:22:55 +02:00
b13eba8008 index.html zu form.html umbenannt 2023-10-07 17:22:18 +02:00
da77201a93 Merge branch 'datenbank_einfügen' 2023-10-07 17:01:58 +02:00
71bf8978ac Eingabemaske funktioniert soweit 2023-10-05 16:50:23 +02:00
18 changed files with 858 additions and 227 deletions

44
.air.toml Normal file
View File

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

1
.gitignore vendored
View File

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

97
create_tables.sql Normal file
View File

@ -0,0 +1,97 @@
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,
document_name VARCHAR(16) 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', 'ICS-2021-LGH', '2021-02-01', '1' ),
( '2023-10-16', '17:05:00', 'Werk Langenhagen', 'ICS-2021-LGH', '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' );

33
main.go
View File

@ -1,38 +1,33 @@
package main package main
import ( import (
"html/template"
"log" "log"
"net/http" "net/http"
"streifling.com/jason/sicherheitsunterweisung/packages/db" "streifling.com/jason/sicherheitsunterweisung/packages/data"
"streifling.com/jason/sicherheitsunterweisung/packages/server" "streifling.com/jason/sicherheitsunterweisung/packages/server"
"streifling.com/jason/sicherheitsunterweisung/packages/types"
) )
func writeBriefing(ch chan *types.Briefing, db *db.DB) {
for b := range ch {
db.WriteBriefing(b)
}
}
func main() { func main() {
var i, j int64 logins := make([]string, 0)
i, j = 1, 1 db, err := data.OpenDB("sicherheitsunterweisung")
mux := http.NewServeMux()
cb := make(chan *types.Briefing)
db, err := db.Open("sicherheitsunterweisung")
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
mux := http.NewServeMux()
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static/")))) mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static/"))))
mux.HandleFunc("/", server.DisplayForm(&i)) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/add-participant/", server.AddParticipant(&i)) template.Must(template.ParseFiles("templates/index.html", "templates/login.html")).Execute(w, nil)
mux.HandleFunc("/submit/", server.SubmitForm(cb, &i, &j)) })
mux.HandleFunc("/search/", server.DisplaySearchResults(db))
go writeBriefing(cb, 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))
log.Fatalln(http.ListenAndServe(":8080", mux)) log.Fatalln(http.ListenAndServe(":8080", mux))
} }

309
packages/data/db.go Normal file
View File

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

View File

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

View File

@ -1,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
}

View File

@ -1,49 +1,193 @@
package server package server
import ( import (
"crypto/rand"
"encoding/hex"
"fmt" "fmt"
"html/template" "html/template"
"log"
"net/http" "net/http"
"strconv"
"time"
"streifling.com/jason/sicherheitsunterweisung/packages/data"
"streifling.com/jason/sicherheitsunterweisung/packages/types" "streifling.com/jason/sicherheitsunterweisung/packages/types"
) )
func DisplayForm(i *int64) http.HandlerFunc { // type questionData struct {
// ID int64
// Q types.Question
// I int
// J int
// }
func DisplayTable(db *data.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
template.Must(template.ParseFiles("templates/index.html")).Execute(w, i) 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 AddParticipant(i *int64) http.HandlerFunc { func DisplaySearchResults(db *data.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
*i++ bs, err := db.GetOverviewTableDataByName(r.PostFormValue("search"))
template.Must(template.ParseFiles("templates/index.html", "templates/participant.html")).ExecuteTemplate(w, "participant", i) 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 SubmitForm(ch chan<- *types.Briefing, i, j *int64) http.HandlerFunc { func DisplayInstructorForm() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
b := new(types.Briefing) template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "content", nil)
}
}
b.FirstName = r.PostFormValue("instructor-first") func generateUUID() (string, error) {
b.LastName = r.PostFormValue("instructor-last") bs := make([]byte, 4)
b.Date = r.PostFormValue("date")
b.Time = r.PostFormValue("time")
b.State = r.PostFormValue("state")
b.Location = r.PostFormValue("location")
for ; *j <= *i; *j++ { if _, err := rand.Read(bs); err != nil {
b.Participants = append(b.Participants, types.Participant{ return "", fmt.Errorf("GenerateUUID: rand.Read(bs): %v\n", err)
ID: *j, }
Person: types.Person{
FirstName: r.PostFormValue("participant-first-" + fmt.Sprint(*j)), return hex.EncodeToString(bs), nil
LastName: r.PostFormValue(("participant-last-" + fmt.Sprint(*j))), }
},
Company: r.PostFormValue(("participant-company-" + fmt.Sprint(*j))), 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)
} }
log.Println(b) (*sl) = append(*sl, login)
ch <- b 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

@ -1,6 +1,7 @@
package types package types
type Person struct { type Person struct {
ID int64
FirstName string FirstName string
LastName string LastName string
} }
@ -8,16 +9,48 @@ type Person struct {
type Instructor Person type Instructor Person
type Participant struct { type Participant struct {
ID int64
Person Person
Company string Company string
} }
type Briefing struct { type Briefing struct {
Instructor ID int64
Date string Date string
Time string Time string
State string
Location string Location string
Participants []Participant DocumentName string
AsOf string
InstructorID int64
}
type Answer struct {
ID int64
Text string
}
type Question struct {
ID int64
Text string
Answers []Answer
Correct int
}
type GivenAnswer struct {
BriefingID int64
ParticipantID int64
QuestionID int64
GivenAnswer int
}
type OverviewTableData struct {
InstructorFirstName string
InstructorLastName string
BriefingDate string
BriefingTime string
BriefingLocation string
BriefingDocumentName string
BriefingAsOf string
ParticipantFirstName string
ParticipantLastName string
ParticipantCompany string
} }

38
templates/briefing.html Normal file
View File

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

View File

@ -11,57 +11,9 @@
<body> <body>
<h1>Sicherheitsunterweisung</h1> <h1>Sicherheitsunterweisung</h1>
<form> <div id="content">
<div id="instructor"> {{ template "content" . }}
<label for="instructor-first-input">Unterweiser Vorname</label> </div>
<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>
<script src="/static/js/htmx.min.js" type="text/javascript"></script> <script src="/static/js/htmx.min.js" type="text/javascript"></script>
</body> </body>

16
templates/login.html Normal file
View File

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

View File

@ -1,5 +1,5 @@
{{ define "participant" }} {{ define "content" }}
<div id="participant-{{ . }}"> <form id="participant-{{ . }}">
<label for="participant-first-input-{{ . }}">Vorname</label> <label for="participant-first-input-{{ . }}">Vorname</label>
<input type="text" name="participant-first-{{ . }}" id="participant-first-input-{{ . }}" /> <input type="text" name="participant-first-{{ . }}" id="participant-first-input-{{ . }}" />
@ -8,5 +8,9 @@
<label for="participant-company-input-{{ . }}">Firma</label> <label for="participant-company-input-{{ . }}">Firma</label>
<input type="text" name="participant-company-{{ . }}" id="participant-company-input-{{ . }}" /> <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 }} {{ end }}

21
templates/question.html Normal file
View File

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

11
templates/results.html Normal file
View File

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

49
templates/table.html Normal file
View 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
View File

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

BIN
tmp/main Executable file

Binary file not shown.