Compare commits

...

8 commits

36 changed files with 345 additions and 244 deletions

View file

@ -7,6 +7,7 @@ import (
"net/mail" "net/mail"
"officeHours/config" "officeHours/config"
"officeHours/models" "officeHours/models"
"officeHours/templating"
"strconv" "strconv"
"strings" "strings"
) )
@ -149,10 +150,7 @@ func (b *BaseHandler) AddOfficeHourHandler(w http.ResponseWriter, req *http.Requ
id, err := b.officeHourRepo.Add(officeHour) id, err := b.officeHourRepo.Add(officeHour)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
templateError := Templates.ExecuteTemplate(w, "addFailure.html", err) templating.ServeTemplate(w, "addFailure", err)
if templateError != nil {
log.Printf("Error executing template addFailure.html: %s", templateError.Error())
}
return return
} }
officeHour, err = b.officeHourRepo.FindById(id) officeHour, err = b.officeHourRepo.FindById(id)
@ -163,10 +161,8 @@ func (b *BaseHandler) AddOfficeHourHandler(w http.ResponseWriter, req *http.Requ
if err != nil { if err != nil {
log.Printf("Error adding request: %s", err.Error()) log.Printf("Error adding request: %s", err.Error())
} }
templateError := Templates.ExecuteTemplate(w, "addSuccess.html", struct{}{}) templating.ServeTemplate(w, "addSuccess", nil)
if templateError != nil {
log.Printf("Error executing template addSuccess.html: %s", templateError.Error())
}
} }
} }
@ -177,10 +173,5 @@ func (b *BaseHandler) writeAddOfficeHourMask(w http.ResponseWriter, req *http.Re
if len(data.Errors) != 0 { if len(data.Errors) != 0 {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
} }
templateError := Templates.ExecuteTemplate(w, "addMask.html", data) templating.ServeTemplate(w, "addMask", data)
if templateError != nil {
log.Printf("Error executing template addMask.html: %s", templateError.Error())
w.Write([]byte(fmt.Sprintf("Template konnte nicht geparst werden : %s", templateError.Error())))
return
}
} }

View file

@ -1,8 +1,8 @@
package controllers package controllers
import ( import (
"log"
"net/http" "net/http"
"officeHours/templating"
) )
func (b *BaseHandler) ConfirmRequestHandler(w http.ResponseWriter, req *http.Request) { func (b *BaseHandler) ConfirmRequestHandler(w http.ResponseWriter, req *http.Request) {
@ -11,24 +11,16 @@ func (b *BaseHandler) ConfirmRequestHandler(w http.ResponseWriter, req *http.Req
if err != nil { if err != nil {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
templateError := Templates.ExecuteTemplate(w, "requestNotFound.html", struct{}{}) templating.ServeTemplate(w, "requestNotFound", nil)
if templateError != nil {
log.Printf("Error executing template requestNotFound.html: %s", templateError.Error())
}
return return
} }
err = b.requestRepo.Execute(request) err = b.requestRepo.Execute(request)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
templateError := Templates.ExecuteTemplate(w, "executeFailure.html", err.Error()) templating.ServeTemplate(w, "executeFailure", err.Error())
if templateError != nil {
log.Printf("Error executing template executeFailure.html: %s", templateError.Error())
}
return return
} }
templateError := Templates.ExecuteTemplate(w, "executeSuccess.html", struct{}{}) templating.ServeTemplate(w, "executeSuccess", nil)
if templateError != nil {
log.Printf("Error executing template executeSuccess.html: %s", templateError.Error())
}
} }

View file

@ -2,13 +2,15 @@
package controllers package controllers
import ( import (
"log"
"net/http" "net/http"
"officeHours/models" "officeHours/models"
"officeHours/templating"
"strconv" "strconv"
) )
func (b *BaseHandler) DeleteOfficeHourHandler(w http.ResponseWriter, req *http.Request) { func (b *BaseHandler) DeleteOfficeHourHandler(w http.ResponseWriter, req *http.Request) {
// TODO: error handling here is by no means sufficient, furthermore
// 400 BadRequest is for technically wrong stuff (most promimently GET instead of POST)
if req.FormValue("id") != "" { if req.FormValue("id") != "" {
id, err := strconv.Atoi(req.FormValue("id")) id, err := strconv.Atoi(req.FormValue("id"))
if err != nil { if err != nil {
@ -19,10 +21,7 @@ func (b *BaseHandler) DeleteOfficeHourHandler(w http.ResponseWriter, req *http.R
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
} }
_, err = b.requestRepo.Add(officeHour, models.RequestDelete) _, err = b.requestRepo.Add(officeHour, models.RequestDelete)
templateError := Templates.ExecuteTemplate(w, "deleteSuccess.html", struct{}{}) templating.ServeTemplate(w, "deleteSuccess", nil)
if templateError != nil {
log.Printf("Error executing template deleteSuccess.html: %s", templateError.Error())
}
} else { } else {
officeHours, _ := b.officeHourRepo.GetAll(true) officeHours, _ := b.officeHourRepo.GetAll(true)
timetable, slots := b.GetTimetable(officeHours) timetable, slots := b.GetTimetable(officeHours)

View file

@ -1,11 +1,11 @@
package controllers package controllers
import ( import (
"fmt"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
"officeHours/models" "officeHours/models"
"officeHours/templating"
"strconv" "strconv"
) )
@ -32,6 +32,7 @@ func (b *BaseHandler) GetByCourseHandler(w http.ResponseWriter, req *http.Reques
courseid, err := strconv.Atoi(req.FormValue("veranstaltung")) courseid, err := strconv.Atoi(req.FormValue("veranstaltung"))
if err != nil { if err != nil {
b.RootHandler(w, req) b.RootHandler(w, req)
return
} }
course, err := b.courseRepo.FindById(courseid) course, err := b.courseRepo.FindById(courseid)
if err != nil { if err != nil {
@ -58,10 +59,5 @@ func (b *BaseHandler) writeTimetablePage(w http.ResponseWriter, req *http.Reques
SelectedRoom int SelectedRoom int
SelectedCourse int SelectedCourse int
}{courses, rooms, timetable, selectedRoom, selectedCourse} }{courses, rooms, timetable, selectedRoom, selectedCourse}
templateError := Templates.ExecuteTemplate(w, "index.html", data) templating.ServeTemplate(w, "index", data)
if templateError != nil {
log.Printf("Error executing template index.html: %s", templateError.Error())
w.Write([]byte(fmt.Sprintf("Template konnte nicht geparst werden : %s", templateError.Error())))
return
}
} }

View file

@ -1,21 +0,0 @@
package controllers
import (
"html/template"
"officeHours/models"
)
var Templates, TemplateError = template.Must(template.ParseFiles(
"templates/addFailure.html",
"templates/addMask.html",
"templates/addSuccess.html",
"templates/deleteSuccess.html",
"templates/executeFailure.html",
"templates/executeSuccess.html",
"templates/footer.html",
"templates/head.html",
"templates/index.html",
"templates/officeHourTable.html",
"templates/requestNotFound.html")).
New("").Funcs(template.FuncMap{"DayName": models.DayName,
"divide": func(i int, j int) int { return i / j }}).ParseFiles("templates/confirmationMail", "templates/td.html")

View file

@ -7,6 +7,7 @@ import (
"html/template" "html/template"
"log" "log"
"officeHours/models" "officeHours/models"
"officeHours/templating"
) )
func (b *BaseHandler) GetTimetable(officeHours []models.OfficeHour) (timetable map[models.Date]map[int]models.OfficeHour, slots []int) { func (b *BaseHandler) GetTimetable(officeHours []models.OfficeHour) (timetable map[models.Date]map[int]models.OfficeHour, slots []int) {
@ -85,9 +86,11 @@ func (b *BaseHandler) printTimetable(timetable map[models.Date]map[int]models.Of
MinuteGranularity int MinuteGranularity int
DeleteIcons bool DeleteIcons bool
}{OfficeHour: current, MinuteGranularity: b.config.Date.MinuteGranularity, DeleteIcons: deleteIcons} }{OfficeHour: current, MinuteGranularity: b.config.Date.MinuteGranularity, DeleteIcons: deleteIcons}
templateError := Templates.ExecuteTemplate(&celldata, "td.html", data) err := templating.WriteTemplate(&celldata, "td", data)
if templateError != nil { if err != nil {
log.Printf("Error executing template td.html: %s", templateError.Error()) err = fmt.Errorf("writing table cell failed:\n%w", err)
log.Println(err.Error())
// TODO: better error wrapping up to top-level request handler
} }
tableBody += celldata.String() tableBody += celldata.String()
} }
@ -120,9 +123,11 @@ func (b *BaseHandler) printTimetable(timetable map[models.Date]map[int]models.Of
slots[4], slots[4],
template.HTML(tableBody), template.HTML(tableBody),
} }
templateError := Templates.ExecuteTemplate(&table, "officeHourTable.html", tableData) err := templating.WriteTemplate(&table, "officeHourTable", tableData)
if templateError != nil { if err != nil {
log.Printf("Error executing template officeHourTable.html: %s", templateError.Error()) err = fmt.Errorf("writing table failed:\n%w", err)
log.Println(err.Error())
// TODO: better error wrapping up to top-level request handler
} }
return template.HTML(table.String()) return template.HTML(table.String())
} }

17
main.go
View file

@ -11,6 +11,7 @@ import (
"officeHours/controllers" "officeHours/controllers"
"officeHours/repositories" "officeHours/repositories"
"officeHours/sqldb" "officeHours/sqldb"
"officeHours/templating"
"os" "os"
) )
@ -20,10 +21,6 @@ func main() {
log.SetOutput(logwriter) log.SetOutput(logwriter)
} }
if controllers.TemplateError != nil {
log.Fatalf("Error parsing templates: %s", controllers.TemplateError.Error())
}
configFile := flag.String( configFile := flag.String(
"config", "config",
"config/config.json", "config/config.json",
@ -40,9 +37,16 @@ func main() {
log.Fatalf("%s: %s", "Reading JSON config file into config structure", err) log.Fatalf("%s: %s", "Reading JSON config file into config structure", err)
} }
// serve static files
staticHandler := http.FileServer(http.Dir("./static"))
// parse templates
if err = templating.InitTemplates(); err != nil {
log.Fatal(err.Error())
}
// database connection
db, err := sqldb.Connect(conf) db, err := sqldb.Connect(conf)
if err != nil { if err != nil {
log.Fatalf(err.Error()) log.Fatal(err.Error())
} }
// Create repos // Create repos
roomRepo := repositories.NewRoomRepo(db) roomRepo := repositories.NewRoomRepo(db)
@ -58,7 +62,8 @@ func main() {
http.HandleFunc("/confirmRequest", h.ConfirmRequestHandler) http.HandleFunc("/confirmRequest", h.ConfirmRequestHandler)
http.HandleFunc("/deleteOfficeHour", h.DeleteOfficeHourHandler) http.HandleFunc("/deleteOfficeHour", h.DeleteOfficeHourHandler)
http.HandleFunc("/", h.RootHandler) http.HandleFunc("/", h.RootHandler)
http.Handle("/static/", http.StripPrefix("/static/", staticHandler))
err = http.ListenAndServe(fmt.Sprintf("%s:%d", conf.Server.ListenAddress, conf.Server.ListenPort), nil) err = http.ListenAndServe(fmt.Sprintf("%s:%d", conf.Server.ListenAddress, conf.Server.ListenPort), nil)
log.Println(err.Error()) log.Fatal(err.Error())
} }

View file

@ -11,8 +11,8 @@ import (
"math/big" "math/big"
"net/smtp" "net/smtp"
"officeHours/config" "officeHours/config"
"officeHours/controllers"
"officeHours/models" "officeHours/models"
"officeHours/templating"
) )
type RequestRepo struct { type RequestRepo struct {
@ -118,7 +118,7 @@ func (r *RequestRepo) newSecret() (string, error) {
for !errors.Is(err, sql.ErrNoRows) { for !errors.Is(err, sql.ErrNoRows) {
secret = randomString(r.config.Request.SecretLength) secret = randomString(r.config.Request.SecretLength)
_, err = r.FindBySecret(secret) _, err = r.FindBySecret(secret)
if err != nil && errors.Is(err, sql.ErrNoRows) { if err != nil && !errors.Is(err, sql.ErrNoRows) {
return "", err return "", err
} }
} }
@ -131,12 +131,13 @@ func (r *RequestRepo) sendConfirmationMail(request models.Request) error {
Config config.Config Config config.Config
Request models.Request Request models.Request
}{r.config, request} }{r.config, request}
err := controllers.Templates.ExecuteTemplate(&message, "confirmationMail", data) err := templating.WriteTemplate(&message, "confirmationMail", data)
if err != nil { if err != nil {
err = fmt.Errorf("Error parsing confirmation Mail: %w", err) err = fmt.Errorf("Error parsing confirmation Mail: %w", err)
log.Println(err.Error()) log.Println(err.Error())
return err return err
} }
switch r.config.Mailer.Type { switch r.config.Mailer.Type {
case "Stdout": case "Stdout":
fmt.Println(message.String()) fmt.Println(message.String())

1
static/bootstrap Symbolic link
View file

@ -0,0 +1 @@
bootstrap-5.2.1-dist/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Sprechstunde anlegen</title>
{{template "head.html" .}}
</head>
<body>
Irgendetwas ist schief gegangen. Bitte sende folgende Daten an <a href="mailto:sprechstundentool@mathebau.de">sprechstundentool@mathebau.de</a> mit einer Beschreibung, was du tun wolltest.
<br />
{{.}}
{{template "footer.html" .}}
</body>
</html>

View file

@ -1,50 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Sprechstunde anlegen</title>
{{template "head.html" .}}
</head>
<body>
<p>
{{range .Errors}}{{.}}<br />{{end}}
</p>
<form method="POST" action="addOfficeHour">
<label for="veranstaltung">Veranstaltung</label>:
<select name="veranstaltung" id="veranstaltung">{{range $course := .Courses}}
<option value="{{$course.Id}}"{{if eq $course.Id $.SelectedCourse}} selected{{end}}>{{$course.Name}}</option>{{end}}
</select><br />
<label for="woche">Woche</label>:
<select name="woche" id="woche">
<option value="0"{{if eq 0 $.Date.Week}} selected{{end}}>Jede</option>
<option value="1"{{if eq 1 $.Date.Week}} selected{{end}}>Ungerade</option>
<option value="2"{{if eq 2 $.Date.Week}} selected{{end}}>Gerade</option>
</select><br />
<label for="tag">Tag</label>: <select name="tag" id="tag">
<option value="0"{{if eq 0 $.Date.Day}} selected{{end}}>Montag</option>
<option value="1"{{if eq 1 $.Date.Day}} selected{{end}}>Dienstag</option>
<option value="2"{{if eq 2 $.Date.Day}} selected{{end}}>Mittwoch</option>
<option value="3"{{if eq 3 $.Date.Day}} selected{{end}}>Donnerstag</option>
<option value="4"{{if eq 4 $.Date.Day}} selected{{end}}>Freitag</option>
</select><br />
<label for="startzeit">Startzeit</label>: <input type="time" name="startzeit" id="startzeit" min="08:00" max="17:30" {{if gt $.Date.Hour 7}}value="{{printf "%02d" $.Date.Hour}}:{{printf "%02d" $.Date.Minute}}"{{end}} required/><br />
<label for="dauer">Dauer in Minuten</label>: <input name="dauer" id="dauer" type="number" min="{{.MinuteGranularity}}" max="120" step="{{.MinuteGranularity}}" value="{{.Duration}}" required/><br />
<label for="raum">Raum</label>:
<select name="raum" id="raum">{{range $room := .Rooms}}
<option value="{{$room.Id}}"{{if eq $room.Id $.SelectedRoom}} selected{{end}}>{{$room.Name}}</option>{{end}}
</select><br />
<label for="raumname">Raumname (für Sonderräume)</label>: <input type="text" name="raumname" id="raumname" value="{{.Roomname}}"/><br />
<label for="name">Name</label>: <input name="name" id="name" type="text" size="50" value="{{.Name}}" required/><br />
<label for="email">Email-Adresse</label>:
<input name="email" id="email" type="email" size="50" value="{{.Email}}" required/><br />
<label for="info">Info</label>: <input name="info" id="info" type="text" size="50" value="{{.Info}}"/><br />
<input type="submit">
</form>
{{if ne .Config.Tutor.MailSuffix ""}}Du musst hier eine Email-Adresse angeben, die auf „{{.Config.Tutor.MailSuffix}}“ endet.<br />{{end}}
Außerdem dürfen in Räumen nur begrenzt viele Sprechstunden gleichzeitig stattfinden, nämlich
<dl>
{{range $room := .Rooms}}
<dt>{{$room.Name}}</dt><dd>{{$room.MaxOccupy}} Sprechstunde{{if gt $room.MaxOccupy 1}}n{{end}}</dd>{{end}}
</dl>
{{template "footer.html" .}}
</body>
</html>

View file

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Sprechstunde anlegen</title>
{{template "head.html" .}}
</head>
<body>
Die Sprechstunde wurde angelegt. Du solltest eine Mail mit einem Aktivierungslink erhalten haben.
<br />
{{template "footer.html" .}}
</body>
</html>

View file

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Sprechstunde löschen</title>
{{template "head.html" .}}
</head>
<body>
Du solltest eine Mail mit einem Bestätigungslink erhalten haben. <br />
Sie wurde an die Adresse geschickt, mit der die Sprechstunde angelegt wurde.
<br />
{{template "footer.html" .}}
</body>
</html>

View file

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Anfrage ausführen fehlgeschlagen</title>
{{template "head.html" .}}
</head>
<body>
Irgendetwas ist schief gegangen. Bitte sende folgende Daten an <a href="mailto:sprechstundentool@mathebau.de">sprechstundentool@mathebau.de</a> mit einer Beschreibung, was du tun wolltest.
<br />
{{.}}
{{template "footer.html" .}}
</body>
</html>

View file

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Anfrage ausgeführt</title>
{{template "head.html" .}}
</head>
<body>
Deine Anfrage wurde ausgeführt. <br />
{{template "footer.html" .}}
</body>
</html>

View file

@ -1,6 +0,0 @@
<footer>
<a href="/">Startseite</a><br />
<a href="/addOfficeHour">Sprechstunde anlegen</a><br />
<a href="/deleteOfficeHour">Sprechstunde löschen</a><br />
Technische Fragen an <a href="mailto:sprechstundentool@mathebau.de">sprechstundentool@mathebau.de</a>
</footer>

View file

@ -1,4 +0,0 @@
<meta charset="UTF-8">
<meta name="keywords" content="Mathebau, Sprechstunde, Sprechstunden, Mathe, Mathematik, technische, Universität, Darmstadt, TU, Fachschaft">
<meta name="description" content="Eine Übersicht der Sprechstunden, die in den offenen Arbeitsräumen der Fachschaft Mathematik, TU Darmstadt, angeboten werden">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View file

@ -1,29 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Sprechstunden</title>
{{template "head.html" .}}
</head>
<body>
<form method="GET" action="/getByCourse">
<label for="veranstaltung">Veranstaltung: </label>
<select name="veranstaltung" id="veranstaltung" size="1" onchange="document.forms[0].submit()">
<option value="">Alle</option>
{{range $course := .Courses}}
<option value="{{$course.Id}}"{{if eq $course.Id $.SelectedCourse}} selected{{end}}>{{$course.Name}}</option>{{end}}
</select>
<input type="submit" value="Auswählen" />
</form>
<form method="GET" action="/getByRoom">
<label for="raum">Raum: </label>
<select name="raum" id="raum" size="1" onchange="document.forms[1].submit()">
<option value="">Alle</option>
{{range $room := .Rooms}}
<option value="{{$room.Id}}"{{if eq $room.Id $.SelectedRoom}} selected{{end}}>{{$room.Name}}</option>{{end}}
</select>
<input type="submit" value="Auswählen" />
</form>
{{.Timetable}}
{{template "footer.html" .}}
</body>
</html>

View file

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Anfrage bestätigen fehlgeschlagen</title>
{{template "head.html" .}}
</head>
<body>
<p>
Dieser Bestätigungscode ist nicht verfügbar. <br />
Bitte gib deinen Bestätigungscode hier ein.
</p>
<form action="/confirmRequest">
<label for="code">Bestätigungscode</label>: <input type="text" name="code" id="code"/>
<input type="submit" />
</form>
{{template "footer.html" .}}
</body>
</html>

138
templating/templates.go Normal file
View file

@ -0,0 +1,138 @@
package templating
import (
"fmt"
"html/template"
"io"
"log"
"net/http"
"officeHours/models"
"os"
"time"
)
// parsed templates available for execution
var templates map[string]*template.Template = map[string]*template.Template{}
// Initialise and parse templates.
//
// Should only be called once.
//
// Since this is something which may error and feels like
// it should not be done automatically on import,
// put it into a function.
func InitTemplates() error {
const templateDir = "templating/templates/"
var funcs = template.FuncMap{
"DayName": models.DayName,
"divide": func(i int, j int) int { return i / j },
}
var emptyTemplate = template.New("").Funcs(funcs)
var baseTemplate, err = template.ParseFiles(templateDir + "base.html")
if err != nil {
return fmt.Errorf("parsing base template failed:\n%w", err)
}
baseTemplate.Funcs(funcs)
type toCache struct {
filename string
standalone bool
}
var toParse = map[string]toCache{
// full html templates
"addFailure": {"addFailure.html", false},
"addMask": {"addMask.html", false},
"addSuccess": {"addSuccess.html", false},
"deleteSuccess": {"deleteSuccess.html", false},
"executeFailure": {"executeFailure.html", false},
"executeSuccess": {"executeSuccess.html", false},
"index": {"index.html", false},
"requestNotFound": {"requestNotFound.html", false},
// standalone templates
"confirmationMail": {"confirmationMail", true},
"officeHourTable": {"officeHourTable.html", true},
"td": {"td.html", true},
}
// parse templates and add to global mapping
for key, tmpl := range toParse {
if _, exists := templates[key]; exists {
return fmt.Errorf("template '%s' already parsed", key)
}
fullName := templateDir + tmpl.filename
// check that template file
info, err := os.Stat(fullName)
if err != nil {
return fmt.Errorf("adding template %s failed:\n%w", tmpl.filename, err)
}
if info.IsDir() {
return fmt.Errorf("adding template %s failed: is a directory", tmpl.filename)
}
// parse
var parsed *template.Template
if tmpl.standalone {
parsed, err = emptyTemplate.Clone()
parsed = parsed.New(tmpl.filename)
} else {
parsed, err = baseTemplate.Clone()
}
if err != nil {
return fmt.Errorf("cloning base template failed:\n%w", err)
}
parsed, err = parsed.ParseFiles(fullName)
if err != nil {
return fmt.Errorf("parsing template %s failed:\n%w", tmpl.filename, err)
}
templates[key] = parsed
}
return nil
}
// Execute a template and write it to the given writer.
//
// Parameters:
// - name: name of the template to execute
// - data: passed through to the template
func WriteTemplate(w io.Writer, name string, data any) error {
tmpl, exists := templates[name]
if !exists {
return fmt.Errorf("template %s not available", name)
}
tmpl, err := tmpl.Clone()
if err != nil {
return fmt.Errorf("cloning template failed:\n%w", err)
}
err = tmpl.Execute(w, data)
if err != nil {
return fmt.Errorf("template execution failed:\n%w", err)
}
return nil
}
// Execute a template and write it to a http writer.
//
// Similar to WriteTemplate, but in error case this adds a corresponding
// status code and writes an error message to the writer.
//
// Typically, this is the final action in handling an http request.
//
// Parameters:
// - name: name of the template to execute
// - data: passed through to the template
func ServeTemplate(w http.ResponseWriter, name string, data any) {
// TODO: make this return an error, handle on top of every request handler
err := WriteTemplate(w, name, data)
if err != nil {
err = fmt.Errorf("writing template failed:\n%w", err)
log.Println(err.Error())
w.WriteHeader(http.StatusInternalServerError)
io.WriteString(w, `Internal Server Error.
Du kannst uns helfen, indem du folgende Fehlermeldung per Mail
an sprechstunden@mathebau.de sendest:
Fehler um\n
`+time.Now().String()+"\n"+err.Error())
return
}
}

View file

@ -0,0 +1,7 @@
{{define "title"}}Fehler{{end}}
{{define "content"}}
Irgendetwas ist schief gegangen. Bitte sende folgende Daten an <a href="mailto:sprechstundentool@mathebau.de">sprechstundentool@mathebau.de</a> mit einer Beschreibung, was du tun wolltest.
<br />
{{.}}
{{end}}

View file

@ -0,0 +1,52 @@
<{{define "title"}}Sprechstunde anlegen{{end}}
{{define "content"}}
<p>
{{range .Errors}}{{.}}<br />{{end}}
</p>
<form method="POST" action="addOfficeHour">
<label for="veranstaltung">Veranstaltung</label>:
<select name="veranstaltung" id="veranstaltung">
{{range $course := .Courses}}
<option value="{{$course.Id}}"{{if eq $course.Id $.SelectedCourse}} selected{{end}}>{{$course.Name}}</option>
{{end}}
</select><br />
<label for="woche">Woche</label>:
<select name="woche" id="woche">
<option value="0"{{if eq 0 $.Date.Week}} selected{{end}}>Jede</option>
<option value="1"{{if eq 1 $.Date.Week}} selected{{end}}>Ungerade</option>
<option value="2"{{if eq 2 $.Date.Week}} selected{{end}}>Gerade</option>
</select><br />
<label for="tag">Tag</label>: <select name="tag" id="tag">
<option value="0"{{if eq 0 $.Date.Day}} selected{{end}}>Montag</option>
<option value="1"{{if eq 1 $.Date.Day}} selected{{end}}>Dienstag</option>
<option value="2"{{if eq 2 $.Date.Day}} selected{{end}}>Mittwoch</option>
<option value="3"{{if eq 3 $.Date.Day}} selected{{end}}>Donnerstag</option>
<option value="4"{{if eq 4 $.Date.Day}} selected{{end}}>Freitag</option>
</select><br />
<label for="startzeit">Startzeit</label>: <input type="time" name="startzeit" id="startzeit" min="08:00" max="17:30" {{if gt $.Date.Hour 7}}value="{{printf "%02d" $.Date.Hour}}:{{printf "%02d" $.Date.Minute}}"{{end}} required/><br />
<label for="dauer">Dauer in Minuten</label>: <input name="dauer" id="dauer" type="number" min="{{.MinuteGranularity}}" max="120" step="{{.MinuteGranularity}}" value="{{.Duration}}" required/><br />
<label for="raum">Raum</label>:
<select name="raum" id="raum">
{{range $room := .Rooms}}
<option value="{{$room.Id}}"{{if eq $room.Id $.SelectedRoom}} selected{{end}}>{{$room.Name}}</option>
{{end}}
</select><br />
<label for="raumname">Raumname (für Sonderräume)</label>: <input type="text" name="raumname" id="raumname" value="{{.Roomname}}"/><br />
<label for="name">Name</label>: <input name="name" id="name" type="text" size="50" value="{{.Name}}" required/><br />
<label for="email">Email-Adresse</label>:
<input name="email" id="email" type="email" size="50" value="{{.Email}}" required/><br />
<label for="info">Info</label>: <input name="info" id="info" type="text" size="50" value="{{.Info}}"/><br />
<input type="submit">
</form>
{{if ne .Config.Tutor.MailSuffix ""}}
Du musst hier eine Email-Adresse angeben, die auf „{{.Config.Tutor.MailSuffix}}“ endet.<br />
{{end}}
Außerdem dürfen in Räumen nur begrenzt viele Sprechstunden gleichzeitig stattfinden, nämlich
<dl>
{{range $room := .Rooms}}
<dt>{{$room.Name}}</dt>
<dd>{{$room.MaxOccupy}} Sprechstunde{{if gt $room.MaxOccupy 1}}n{{end}}</dd>
{{end}}
</dl>
{{end}}

View file

@ -0,0 +1,5 @@
{{define "title"}}Sprechstunde anlegen{{end}}
{{define "content"}}
Die Sprechstunde wurde angelegt. Du solltest eine Mail mit einem Aktivierungslink erhalten haben.
{{end}}

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="keywords" content="Mathebau, Sprechstunde, Sprechstunden, Mathe, Mathematik, technische, Universität, Darmstadt, TU, Fachschaft">
<meta name="description" content="Eine Übersicht der Sprechstunden, die in den offenen Arbeitsräumen der Fachschaft Mathematik, TU Darmstadt, angeboten werden">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<title>{{block "title" .}}Start{{end}} Sprechstunden</title>
</head>
<body>
<div class="container">
{{block "content" .}}Du solltest dies nicht sehen.{{end}}
</div>
<footer class="container">
<a href="/">Startseite</a><br />
<a href="/addOfficeHour">Sprechstunde anlegen</a><br />
<a href="/deleteOfficeHour">Sprechstunde löschen</a><br />
Technische Fragen an <a href="mailto:sprechstundentool@mathebau.de">sprechstundentool@mathebau.de</a>
</footer>
<script src="/static/bootstrap/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View file

@ -0,0 +1,7 @@
{{define "title"}}Sprechstunde löschen{{end}}
{{define "content"}}
Du solltest eine Mail mit einem Bestätigungslink erhalten haben. <br />
Sie wurde an die Adresse geschickt, mit der die Sprechstunde angelegt wurde.
<br />
{{end}}

View file

@ -0,0 +1,7 @@
{{define "title"}}Anfrage ausführen fehlgeschlagen{{end}}
{{define "content"}}
Irgendetwas ist schief gegangen. Bitte sende folgende Daten an <a href="mailto:sprechstundentool@mathebau.de">sprechstundentool@mathebau.de</a> mit einer Beschreibung, was du tun wolltest.
<br />
{{.}}
{{end}}

View file

@ -0,0 +1,5 @@
{{define "title"}}Anfrage ausgeführt{{end}}
{{define "content"}}
Deine Anfrage wurde ausgeführt.
{{end}}

View file

@ -0,0 +1,25 @@
{{define "title"}}Übersicht{{end}}
{{define "content"}}
<form method="GET" action="/getByCourse">
<label for="veranstaltung">Veranstaltung: </label>
<select name="veranstaltung" id="veranstaltung" size="1" onchange="document.forms[0].submit()">
<option value="">Alle</option>
{{range $course := .Courses}}
<option value="{{$course.Id}}"{{if eq $course.Id $.SelectedCourse}} selected{{end}}>{{$course.Name}}</option>
{{end}}
</select>
<input type="submit" value="Auswählen" />
</form>
<form method="GET" action="/getByRoom">
<label for="raum">Raum: </label>
<select name="raum" id="raum" size="1" onchange="document.forms[1].submit()">
<option value="">Alle</option>
{{range $room := .Rooms}}
<option value="{{$room.Id}}"{{if eq $room.Id $.SelectedRoom}} selected{{end}}>{{$room.Name}}</option>
{{end}}
</select>
<input type="submit" value="Auswählen" />
</form>
{{.Timetable}}
{{end}}

View file

@ -0,0 +1,12 @@
{{define "title"}}Anfrage bestätigen fehlgeschlagen{{end}}
{{define "content"}}
<p>
Dieser Bestätigungscode ist nicht verfügbar. <br />
Bitte gib deinen Bestätigungscode hier ein.
</p>
<form action="/confirmRequest">
<label for="code">Bestätigungscode</label>: <input type="text" name="code" id="code"/>
<input type="submit" />
</form>
{{end}}