Verbessere Dokumentation
This commit is contained in:
parent
ec24c6c4dc
commit
fe54d76ab2
14 changed files with 75 additions and 34 deletions
|
@ -1,3 +1,7 @@
|
|||
// Package config implements the officeHours configuration
|
||||
//
|
||||
// It provides a struct type holding all necessary information for the programm.
|
||||
// The configuration can be read from a JSON file.
|
||||
package config
|
||||
|
||||
import (
|
||||
|
@ -8,32 +12,34 @@ import (
|
|||
"log"
|
||||
)
|
||||
|
||||
// A Config holds all the constants for the programm.
|
||||
// It is passed to most repositories.
|
||||
type Config struct {
|
||||
Server struct {
|
||||
ListenAddress string
|
||||
ListenPort int
|
||||
Protocol string
|
||||
Domain string
|
||||
ListenAddress string // Address on which the http server is supposed to listen
|
||||
ListenPort int // Port on which the http server is supposed to listen
|
||||
Protocol string // Protocol to access the server; either "http" or "https"
|
||||
Domain string // A string indicating the tool's base domain and path, e.g. "example.com/subfolder"
|
||||
}
|
||||
Date struct {
|
||||
MinuteGranularity int
|
||||
MinuteGranularity int // Restricts the minutes on which office hours can start and end to multiples of it.
|
||||
}
|
||||
Request struct {
|
||||
SecretLength int
|
||||
SecretLength int // Length of the secret token for requests
|
||||
}
|
||||
Mailer struct {
|
||||
Type string
|
||||
FromAddress string
|
||||
FromName template.HTML
|
||||
SmtpHost string
|
||||
SmtpPort int
|
||||
SmtpUseAuth bool
|
||||
SmtpIdentity string
|
||||
SmtpPassword string
|
||||
Type string // Send mail to "Stdout" or "Smtp"
|
||||
FromAddress string // Send mail from this address
|
||||
FromName template.HTML // Send mail from this name, e.g. "Office hours <officeHours@localhost>"
|
||||
SmtpHost string // Host to use as smarthost
|
||||
SmtpPort int // Port of the smarthost
|
||||
SmtpUseAuth bool // Set whether to use authentication on the smarthosthost
|
||||
SmtpIdentity string // Smarthost username
|
||||
SmtpPassword string // Smarthost password
|
||||
}
|
||||
SQL struct {
|
||||
Type string
|
||||
SQLiteFile string
|
||||
Type string // Can be "SQLite" or "Mysql"
|
||||
SQLiteFile string // Path to SQLite file
|
||||
MysqlUser string
|
||||
MysqlPassword string
|
||||
MysqlHost string
|
||||
|
@ -41,7 +47,8 @@ type Config struct {
|
|||
MysqlDatabase string
|
||||
}
|
||||
Tutor struct {
|
||||
MailSuffix string
|
||||
MailSuffix string // Restrict mailaddresses of tutors to suffixes.
|
||||
// e.g. "example.com" allowes "foo@example.com", "foo@sub.example.com", but not "foo@another.org" or "foo@barexample.com".
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,5 +99,4 @@ func validateConfig(conf *Config) error {
|
|||
log.Println(err.Error())
|
||||
}
|
||||
return err
|
||||
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ type maskData struct {
|
|||
Config config.Config
|
||||
}
|
||||
|
||||
// Offer a form to add office hours and validate its input on receiving.
|
||||
func (b *BaseHandler) AddOfficeHourHandler(w http.ResponseWriter, req *http.Request) {
|
||||
var errors []string
|
||||
courses, err := b.courseRepo.GetAll()
|
||||
|
@ -123,6 +124,9 @@ func (b *BaseHandler) AddOfficeHourHandler(w http.ResponseWriter, req *http.Requ
|
|||
} else if !allowed {
|
||||
errors = append(errors, "In dem Raum muss noch Platz für weitere Sprechstunden sein.")
|
||||
}
|
||||
|
||||
// If there were errors in the data for the new office hour,
|
||||
// answer with the form prefilled with the sent data.
|
||||
if len(errors) != 0 {
|
||||
var data maskData = maskData{
|
||||
courses,
|
||||
|
@ -141,6 +145,8 @@ func (b *BaseHandler) AddOfficeHourHandler(w http.ResponseWriter, req *http.Requ
|
|||
}
|
||||
b.writeAddOfficeHourMask(w, req, data)
|
||||
} else {
|
||||
// if the data for a new office hour was sent correctly, save it.
|
||||
|
||||
officeHour := models.OfficeHour{Id: 0,
|
||||
Tutor: models.Tutor{Id: 0, Name: name, Email: email.Address},
|
||||
Date: date,
|
||||
|
@ -166,12 +172,11 @@ func (b *BaseHandler) AddOfficeHourHandler(w http.ResponseWriter, req *http.Requ
|
|||
log.Printf("Error adding request: %s", err.Error())
|
||||
}
|
||||
templating.ServeTemplate(w, "addSuccess", nil)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BaseHandler) writeAddOfficeHourMask(w http.ResponseWriter, req *http.Request, data maskData) {
|
||||
if req.Method == http.MethodGet {
|
||||
if req.Method == http.MethodGet { // if the current request is GET, we assume no office hour addition was tried so far and reset the errors.
|
||||
data.Errors = []string{}
|
||||
}
|
||||
if len(data.Errors) != 0 {
|
||||
|
|
|
@ -1,19 +1,27 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/http"
|
||||
"officeHours/templating"
|
||||
)
|
||||
|
||||
// Check the secret token for requests and execute the request for correct tokens
|
||||
func (b *BaseHandler) ConfirmRequestHandler(w http.ResponseWriter, req *http.Request) {
|
||||
secret := req.FormValue("code")
|
||||
request, err := b.requestRepo.FindBySecret(secret)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) { // There was no request with this secret
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
templating.ServeTemplate(w, "requestNotFound", nil)
|
||||
return
|
||||
}
|
||||
if err != nil { // Some other error happened finding the request with this secret
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
templating.ServeTemplate(w, "executeFailure", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = b.requestRepo.Execute(request)
|
||||
if err != nil {
|
||||
|
@ -22,5 +30,4 @@ func (b *BaseHandler) ConfirmRequestHandler(w http.ResponseWriter, req *http.Req
|
|||
return
|
||||
}
|
||||
templating.ServeTemplate(w, "executeSuccess", nil)
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,9 @@ import (
|
|||
"strconv"
|
||||
)
|
||||
|
||||
// Offer a table of all office hours to delete,
|
||||
// verify the corresponding mail address and
|
||||
// then send a confirmation mail.
|
||||
func (b *BaseHandler) DeleteOfficeHourHandler(w http.ResponseWriter, req *http.Request) {
|
||||
if req.FormValue("id") != "" {
|
||||
id, err := strconv.Atoi(req.FormValue("id"))
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// course
|
||||
package models
|
||||
|
||||
type Course struct {
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
// date
|
||||
package models
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
type Date struct {
|
||||
Week int
|
||||
Week int // Set whether the date is all weeks (0), odd weeks (1) or even weeks (2).
|
||||
Day int
|
||||
Hour int
|
||||
Minute int
|
||||
}
|
||||
|
||||
// Return the name of the day for a given integer.
|
||||
func DayName(day int) string {
|
||||
switch day {
|
||||
case 0:
|
||||
|
@ -21,10 +25,12 @@ func DayName(day int) string {
|
|||
case 4:
|
||||
return "Freitag"
|
||||
default:
|
||||
log.Printf("No day name found for day %d", day)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Compare whether first date is strictly before second date.
|
||||
func DateLess(first Date, second Date) bool {
|
||||
if first.Day < second.Day {
|
||||
return true
|
||||
|
@ -44,6 +50,7 @@ func DateLess(first Date, second Date) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// Get the end date for some duration.
|
||||
func GetEndDate(date Date, duration int, ignoreWeek bool) Date {
|
||||
var endDate Date
|
||||
if ignoreWeek {
|
||||
|
@ -51,6 +58,7 @@ func GetEndDate(date Date, duration int, ignoreWeek bool) Date {
|
|||
} else {
|
||||
endDate = Date{date.Week, date.Day, date.Hour, date.Minute}
|
||||
}
|
||||
endDate.Day = (endDate.Day + (endDate.Hour*60+endDate.Minute+duration)/(60*24)) % 7
|
||||
endDate.Hour = endDate.Hour + (endDate.Minute+duration)/60
|
||||
endDate.Minute = (endDate.Minute + duration) % 60
|
||||
return endDate
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// officeHour
|
||||
// The package models defines the stucts for objects, their repositories and the signatures of their functions.
|
||||
package models
|
||||
|
||||
type OfficeHour struct {
|
||||
|
@ -7,10 +7,10 @@ type OfficeHour struct {
|
|||
Date
|
||||
Room
|
||||
Course
|
||||
RoomName string
|
||||
RoomName string // A description of the room for special rooms that are not preconfigured.
|
||||
Info string
|
||||
Active bool
|
||||
Duration int
|
||||
Duration int // Duration in minutes. Must be divisible by the config field "MinuteGranularity".
|
||||
}
|
||||
|
||||
type OfficeHourRepository interface {
|
||||
|
|
|
@ -8,8 +8,8 @@ type Request struct {
|
|||
Secret string
|
||||
}
|
||||
|
||||
const RequestActivate int = 1
|
||||
const RequestDelete int = 2
|
||||
const RequestActivate int = 1 // Fix integer to represent request for activation of an office hour.
|
||||
const RequestDelete int = 2 // Fix integer to represent request for deletion of an office hour.
|
||||
|
||||
type RequestRepository interface {
|
||||
Add(officeHour OfficeHour, action int) (int, error)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// raum
|
||||
package models
|
||||
|
||||
type Room struct {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// course
|
||||
package repositories
|
||||
|
||||
import (
|
||||
|
@ -9,6 +8,7 @@ import (
|
|||
"officeHours/models"
|
||||
)
|
||||
|
||||
// A struct to hold the db connection
|
||||
type CourseRepo struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ func (r *CourseRepo) GetAll() ([]models.Course, error) {
|
|||
return r.getFromRows(rows)
|
||||
}
|
||||
|
||||
// Helper function to get a course from multiple SQL result rows
|
||||
func (r *CourseRepo) getFromRows(rows *sql.Rows) ([]models.Course, error) {
|
||||
var courses []models.Course
|
||||
for rows.Next() {
|
||||
|
@ -51,6 +52,7 @@ func (r *CourseRepo) getFromRows(rows *sql.Rows) ([]models.Course, error) {
|
|||
return courses, nil
|
||||
}
|
||||
|
||||
// Helper function to get a course from an SQL result row
|
||||
func (r *CourseRepo) getFromRow(row *sql.Row) (models.Course, error) {
|
||||
var course models.Course
|
||||
if err := row.Scan(&course.Id, &course.Name); err != nil {
|
||||
|
|
|
@ -89,6 +89,10 @@ func (r *OfficeHourRepo) FindById(id int) (models.OfficeHour, error) {
|
|||
return r.getFromRow(r.db.QueryRow("SELECT * FROM officeHour WHERE id=?", id))
|
||||
}
|
||||
|
||||
// Add an office hour if it doesn't exist yet.
|
||||
// Also add the incluyey tutor if it doesn't exist yet.
|
||||
//
|
||||
// Returns the id of the new office hour.
|
||||
func (r *OfficeHourRepo) Add(officeHour models.OfficeHour) (int, error) {
|
||||
// Find correct tutor or add if not existent
|
||||
_, err := r.tutorRepo.Add(officeHour.Tutor)
|
||||
|
@ -215,6 +219,7 @@ func (r *OfficeHourRepo) getFromRows(rows *sql.Rows) ([]models.OfficeHour, error
|
|||
return officeHours, nil
|
||||
}
|
||||
|
||||
// Get the number of office hours that are maximally parallel during a time span.
|
||||
func (r *OfficeHourRepo) NumberByTimeSpanAndRoom(date models.Date, duration int, room models.Room, activeOnly bool) (int, error) {
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
|
@ -252,6 +257,7 @@ func (r *OfficeHourRepo) NumberByTimeSpanAndRoom(date models.Date, duration int,
|
|||
return count, nil
|
||||
}
|
||||
|
||||
// Check whether the room capacity allows for another office hour during a time span.
|
||||
func (r *OfficeHourRepo) AllowedAt(date models.Date, duration int, room models.Room, activeOnly bool) (bool, error) {
|
||||
numberOfOfficeHours, err := r.NumberByTimeSpanAndRoom(date, duration, room, activeOnly)
|
||||
if err != nil {
|
||||
|
|
|
@ -68,6 +68,8 @@ func (r *RequestRepo) FindByOfficeHour(officeHour models.OfficeHour) ([]models.R
|
|||
return requests, nil
|
||||
}
|
||||
|
||||
// Add a request to the database if it doesnt already exist.
|
||||
// Send a mail with the secret to the confirmation address in any case.
|
||||
func (r *RequestRepo) Add(officeHour models.OfficeHour, action int) (int, error) {
|
||||
existents, err := r.FindByOfficeHour(officeHour)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
|
@ -97,6 +99,7 @@ func (r *RequestRepo) Add(officeHour models.OfficeHour, action int) (int, error)
|
|||
return request.Id, r.sendConfirmationMail(request)
|
||||
}
|
||||
|
||||
// Execute a request and delete it.
|
||||
func (r *RequestRepo) Execute(request models.Request) error {
|
||||
var err error
|
||||
switch request.Action {
|
||||
|
@ -107,11 +110,13 @@ func (r *RequestRepo) Execute(request models.Request) error {
|
|||
err = r.officeHourRepo.Delete(request.OfficeHour)
|
||||
r.db.Exec("DELETE FROM request WHERE officeHour=?", request.OfficeHour.Id)
|
||||
default:
|
||||
log.Printf("Executing request: Action type %d unknown.", request.Action)
|
||||
_, err = r.db.Exec("DELETE FROM request WHERE id=?", request.Id)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Find a new secret token with configured length that is currently unused.
|
||||
func (r *RequestRepo) newSecret() (string, error) {
|
||||
var err error
|
||||
var secret string
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// raum
|
||||
package repositories
|
||||
|
||||
import (
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// Connect to a database using or throw an error
|
||||
func Connect(config config.Config) (*sql.DB, error) {
|
||||
switch config.SQL.Type {
|
||||
case "SQLite":
|
||||
|
@ -24,6 +25,7 @@ func Connect(config config.Config) (*sql.DB, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Connect to a SQLite database and check the connection.
|
||||
func connectSQLite(file string) (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite3", file)
|
||||
if err != nil {
|
||||
|
@ -32,6 +34,7 @@ func connectSQLite(file string) (*sql.DB, error) {
|
|||
return db, nil
|
||||
}
|
||||
|
||||
// Connect to a Mysql database and check the connection.
|
||||
func connectMysql(user string, password string, address string, port int, database string) (*sql.DB, error) {
|
||||
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", user, password, address, port, database))
|
||||
if err != nil {
|
||||
|
@ -39,7 +42,6 @@ func connectMysql(user string, password string, address string, port int, databa
|
|||
}
|
||||
|
||||
err = db.Ping()
|
||||
// handle error
|
||||
if err != nil {
|
||||
return db, fmt.Errorf("Error pinging Mysql database: %w", err)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue