2022-11-04 20:15:38 +00:00
// 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.
2022-09-19 12:46:16 +00:00
package config
import (
"encoding/json"
"fmt"
"html/template"
"io/ioutil"
"log"
2022-11-18 09:17:38 +00:00
"net/mail"
2022-09-19 12:46:16 +00:00
)
2022-11-04 20:15:38 +00:00
// A Config holds all the constants for the programm.
// It is passed to most repositories.
2022-09-19 12:46:16 +00:00
type Config struct {
Server struct {
2022-11-04 20:15:38 +00:00
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"
2022-09-19 12:46:16 +00:00
}
Date struct {
2022-11-04 20:15:38 +00:00
MinuteGranularity int // Restricts the minutes on which office hours can start and end to multiples of it.
2023-10-26 09:29:42 +00:00
MaxDuration int // Limits the length of office hours to minutes
2022-11-15 10:42:23 +00:00
EarliestStartTime struct {
Hour int
Minute int
}
LatestStartTime struct {
Hour int
Minute int
}
2022-09-19 12:46:16 +00:00
}
Request struct {
2022-11-04 20:15:38 +00:00
SecretLength int // Length of the secret token for requests
2022-09-19 12:46:16 +00:00
}
Mailer struct {
2022-11-04 20:15:38 +00:00
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
2022-09-19 12:46:16 +00:00
}
SQL struct {
2022-11-04 20:15:38 +00:00
Type string // Can be "SQLite" or "Mysql"
SQLiteFile string // Path to SQLite file
2022-09-19 12:46:16 +00:00
MysqlUser string
MysqlPassword string
MysqlHost string
MysqlPort int
MysqlDatabase string
}
2022-09-19 15:00:19 +00:00
Tutor struct {
2022-11-04 20:15:38 +00:00
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".
2022-09-19 15:00:19 +00:00
}
2022-09-19 12:46:16 +00:00
}
// ReadConfigFile takes a file path as an argument and attempts to
// unmarshal the content of the file into a struct containing a
// configuration.
func ReadConfigFile ( filename string , conf * Config ) error {
configData , err := ioutil . ReadFile ( filename )
if err != nil {
2022-09-21 20:24:08 +00:00
err = fmt . Errorf ( "Error reading config file: %w" , err )
log . Println ( err . Error ( ) )
2022-09-19 12:46:16 +00:00
return err
}
err = json . Unmarshal ( configData , conf )
if err != nil {
2022-09-21 20:24:08 +00:00
err = fmt . Errorf ( "Error parsing config file as json: %w" , err )
log . Println ( err . Error ( ) )
2022-09-19 12:46:16 +00:00
return err
}
return validateConfig ( conf )
}
2022-09-19 15:00:19 +00:00
// Checks config for sane values (e.g. correct port range or database type).
2022-09-19 12:46:16 +00:00
func validateConfig ( conf * Config ) error {
var err error
if ! ( conf . Server . ListenPort >= 1 && conf . Server . ListenPort <= 65535 ) {
err = fmt . Errorf ( "Validating config: Server port must be between 1 and 65535, but is %d." , conf . Server . ListenPort )
log . Println ( err . Error ( ) )
}
if ! ( conf . Server . Protocol == "http" || conf . Server . Protocol == "https" ) {
2022-09-19 15:00:19 +00:00
err = fmt . Errorf ( "Validating config: Server protocol must be 'http' or 'https', but is '%s'." , conf . Server . Protocol )
2022-09-19 12:46:16 +00:00
log . Println ( err . Error ( ) )
}
if ! ( conf . Date . MinuteGranularity >= 1 && conf . Date . MinuteGranularity <= 60 ) {
err = fmt . Errorf ( "Validating config: Minute granularity must be between 1 and 60, but is %d." , conf . Date . MinuteGranularity )
log . Println ( err . Error ( ) )
}
2022-11-15 10:42:23 +00:00
if ! ( conf . Date . EarliestStartTime . Hour >= 0 && conf . Date . EarliestStartTime . Hour <= 23 ) {
err = fmt . Errorf ( "Validating config: Earliest start time hour must be between 0 and 23, but is %d." , conf . Date . EarliestStartTime . Hour )
log . Println ( err . Error ( ) )
}
if ! ( conf . Date . EarliestStartTime . Minute >= 0 && conf . Date . EarliestStartTime . Minute <= 60 ) {
err = fmt . Errorf ( "Validating config: Earliest start time minute must be between 0 and 60, but is %d." , conf . Date . EarliestStartTime . Minute )
log . Println ( err . Error ( ) )
}
2023-10-26 09:29:42 +00:00
if ! ( conf . Date . MaxDuration >= conf . Date . MinuteGranularity && conf . Date . MaxDuration <= 4 * 60 ) {
err = fmt . Errorf ( "Validating config: Maximum duration must be between %d minute and 4 hours, but is %d." , conf . Date . MinuteGranularity , conf . Date . MaxDuration )
log . Println ( err . Error ( ) )
}
2022-11-15 10:42:23 +00:00
if ! ( conf . Date . LatestStartTime . Hour >= 0 && conf . Date . LatestStartTime . Hour <= 23 ) {
err = fmt . Errorf ( "Validating config: Latest start time hour must be between 0 and 23, but is %d." , conf . Date . LatestStartTime . Hour )
log . Println ( err . Error ( ) )
}
if ! ( conf . Date . LatestStartTime . Minute >= 0 && conf . Date . LatestStartTime . Minute <= 60 ) {
err = fmt . Errorf ( "Validating config: Latest start time minute must be between 0 and 60, but is %d." , conf . Date . LatestStartTime . Minute )
log . Println ( err . Error ( ) )
}
if ! ( conf . Date . EarliestStartTime . Hour < conf . Date . LatestStartTime . Hour || ( conf . Date . EarliestStartTime . Hour == conf . Date . LatestStartTime . Hour && conf . Date . EarliestStartTime . Minute < conf . Date . LatestStartTime . Minute ) ) {
err = fmt . Errorf ( "Validating config: Latest start time minute must be after earliest start time." )
log . Println ( err . Error ( ) )
}
2022-09-19 12:46:16 +00:00
if ! ( conf . Request . SecretLength >= 5 && conf . Request . SecretLength <= 50 ) {
err = fmt . Errorf ( "Validating config: Requests' secret length must be between 5 and 50, but is %d." , conf . Request . SecretLength )
log . Println ( err . Error ( ) )
}
if ! ( conf . Mailer . Type == "Stdout" || conf . Mailer . Type == "Smtp" ) {
2022-09-19 15:00:19 +00:00
err = fmt . Errorf ( "Validating config: Mailer type must be 'Stdout' or 'Smtp', but is '%s'." , conf . Mailer . Type )
2022-09-19 12:46:16 +00:00
log . Println ( err . Error ( ) )
}
2022-11-18 09:17:38 +00:00
mailFromAddress , mailFromAddressErr := mail . ParseAddress ( conf . Mailer . FromAddress )
if ! ( mailFromAddressErr == nil ) {
err = fmt . Errorf ( "Validating config: Mail FromAddress could not be parsed (%w)" , mailFromAddressErr )
log . Println ( err )
} else {
if ! ( mailFromAddress . Name == "" ) {
err = fmt . Errorf ( "Validating config: Mail FromAddress must not contain a name value, but has '%s'" , mailFromAddress . Name )
log . Println ( err )
}
}
_ , mailFromNameErr := mail . ParseAddress ( string ( conf . Mailer . FromName ) )
if ! ( mailFromNameErr == nil ) {
err = fmt . Errorf ( "Validating config: Mail FromName could not be parsed (%w)" , mailFromNameErr )
log . Println ( err )
}
2022-09-19 12:46:16 +00:00
if ! ( conf . SQL . Type == "SQLite" || conf . SQL . Type == "Mysql" ) {
err = fmt . Errorf ( "Validating config: SQL type must be 'SQLite' or 'Mysql', but is '%s'." , conf . SQL . Type )
log . Println ( err . Error ( ) )
}
return err
}