Compare commits
No commits in common. "c9883b0d3f6f432df09b44fc578f3f579bfe134d" and "6e97d867de8eacbfb239bac411a29a0a960d9b41" have entirely different histories.
c9883b0d3f
...
6e97d867de
36 changed files with 244 additions and 345 deletions
|
@ -7,7 +7,6 @@ import (
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"officeHours/config"
|
"officeHours/config"
|
||||||
"officeHours/models"
|
"officeHours/models"
|
||||||
"officeHours/templating"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -150,7 +149,10 @@ 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)
|
||||||
templating.ServeTemplate(w, "addFailure", err)
|
templateError := Templates.ExecuteTemplate(w, "addFailure.html", 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)
|
||||||
|
@ -161,8 +163,10 @@ 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())
|
||||||
}
|
}
|
||||||
templating.ServeTemplate(w, "addSuccess", nil)
|
templateError := Templates.ExecuteTemplate(w, "addSuccess.html", struct{}{})
|
||||||
|
if templateError != nil {
|
||||||
|
log.Printf("Error executing template addSuccess.html: %s", templateError.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,5 +177,10 @@ 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)
|
||||||
}
|
}
|
||||||
templating.ServeTemplate(w, "addMask", data)
|
templateError := Templates.ExecuteTemplate(w, "addMask.html", 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,16 +11,24 @@ func (b *BaseHandler) ConfirmRequestHandler(w http.ResponseWriter, req *http.Req
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
templating.ServeTemplate(w, "requestNotFound", nil)
|
templateError := Templates.ExecuteTemplate(w, "requestNotFound.html", struct{}{})
|
||||||
|
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)
|
||||||
templating.ServeTemplate(w, "executeFailure", err.Error())
|
templateError := Templates.ExecuteTemplate(w, "executeFailure.html", err.Error())
|
||||||
|
if templateError != nil {
|
||||||
|
log.Printf("Error executing template executeFailure.html: %s", templateError.Error())
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
templating.ServeTemplate(w, "executeSuccess", nil)
|
templateError := Templates.ExecuteTemplate(w, "executeSuccess.html", struct{}{})
|
||||||
|
if templateError != nil {
|
||||||
|
log.Printf("Error executing template executeSuccess.html: %s", templateError.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,13 @@
|
||||||
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 {
|
||||||
|
@ -21,7 +19,10 @@ 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)
|
||||||
templating.ServeTemplate(w, "deleteSuccess", nil)
|
templateError := Templates.ExecuteTemplate(w, "deleteSuccess.html", struct{}{})
|
||||||
|
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)
|
||||||
|
|
|
@ -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,7 +32,6 @@ 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 {
|
||||||
|
@ -59,5 +58,10 @@ 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}
|
||||||
templating.ServeTemplate(w, "index", data)
|
templateError := Templates.ExecuteTemplate(w, "index.html", 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
21
controllers/templates.go
Normal file
21
controllers/templates.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
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")
|
|
@ -7,7 +7,6 @@ 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) {
|
||||||
|
@ -86,11 +85,9 @@ 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}
|
||||||
err := templating.WriteTemplate(&celldata, "td", data)
|
templateError := Templates.ExecuteTemplate(&celldata, "td.html", data)
|
||||||
if err != nil {
|
if templateError != nil {
|
||||||
err = fmt.Errorf("writing table cell failed:\n%w", err)
|
log.Printf("Error executing template td.html: %s", templateError.Error())
|
||||||
log.Println(err.Error())
|
|
||||||
// TODO: better error wrapping up to top-level request handler
|
|
||||||
}
|
}
|
||||||
tableBody += celldata.String()
|
tableBody += celldata.String()
|
||||||
}
|
}
|
||||||
|
@ -123,11 +120,9 @@ func (b *BaseHandler) printTimetable(timetable map[models.Date]map[int]models.Of
|
||||||
slots[4],
|
slots[4],
|
||||||
template.HTML(tableBody),
|
template.HTML(tableBody),
|
||||||
}
|
}
|
||||||
err := templating.WriteTemplate(&table, "officeHourTable", tableData)
|
templateError := Templates.ExecuteTemplate(&table, "officeHourTable.html", tableData)
|
||||||
if err != nil {
|
if templateError != nil {
|
||||||
err = fmt.Errorf("writing table failed:\n%w", err)
|
log.Printf("Error executing template officeHourTable.html: %s", templateError.Error())
|
||||||
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
17
main.go
|
@ -11,7 +11,6 @@ import (
|
||||||
"officeHours/controllers"
|
"officeHours/controllers"
|
||||||
"officeHours/repositories"
|
"officeHours/repositories"
|
||||||
"officeHours/sqldb"
|
"officeHours/sqldb"
|
||||||
"officeHours/templating"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,6 +20,10 @@ 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",
|
||||||
|
@ -37,16 +40,9 @@ 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.Fatal(err.Error())
|
log.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
// Create repos
|
// Create repos
|
||||||
roomRepo := repositories.NewRoomRepo(db)
|
roomRepo := repositories.NewRoomRepo(db)
|
||||||
|
@ -62,8 +58,7 @@ 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.Fatal(err.Error())
|
log.Println(err.Error())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,13 +131,12 @@ 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 := templating.WriteTemplate(&message, "confirmationMail", data)
|
err := controllers.Templates.ExecuteTemplate(&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 +0,0 @@
|
||||||
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
13
templates/addFailure.html
Normal file
13
templates/addFailure.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!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>
|
50
templates/addMask.html
Normal file
50
templates/addMask.html
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<!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>
|
12
templates/addSuccess.html
Normal file
12
templates/addSuccess.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<!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>
|
13
templates/deleteSuccess.html
Normal file
13
templates/deleteSuccess.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!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>
|
13
templates/executeFailure.html
Normal file
13
templates/executeFailure.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!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>
|
11
templates/executeSuccess.html
Normal file
11
templates/executeSuccess.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<!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>
|
6
templates/footer.html
Normal file
6
templates/footer.html
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<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>
|
4
templates/head.html
Normal file
4
templates/head.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<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">
|
29
templates/index.html
Normal file
29
templates/index.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<!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>
|
18
templates/requestNotFound.html
Normal file
18
templates/requestNotFound.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<!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>
|
|
@ -1,138 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{{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}}
|
|
|
@ -1,52 +0,0 @@
|
||||||
<{{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}}
|
|
|
@ -1,5 +0,0 @@
|
||||||
{{define "title"}}Sprechstunde anlegen{{end}}
|
|
||||||
|
|
||||||
{{define "content"}}
|
|
||||||
Die Sprechstunde wurde angelegt. Du solltest eine Mail mit einem Aktivierungslink erhalten haben.
|
|
||||||
{{end}}
|
|
|
@ -1,27 +0,0 @@
|
||||||
<!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>
|
|
|
@ -1,7 +0,0 @@
|
||||||
{{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}}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{{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}}
|
|
|
@ -1,5 +0,0 @@
|
||||||
{{define "title"}}Anfrage ausgeführt{{end}}
|
|
||||||
|
|
||||||
{{define "content"}}
|
|
||||||
Deine Anfrage wurde ausgeführt.
|
|
||||||
{{end}}
|
|
|
@ -1,25 +0,0 @@
|
||||||
{{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}}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{{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}}
|
|
Loading…
Reference in a new issue