Verbessere Dokumentation

This commit is contained in:
Gonne 2022-11-04 21:15:38 +01:00
parent ec24c6c4dc
commit fe54d76ab2
14 changed files with 75 additions and 34 deletions

View File

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

View File

@ -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 {

View File

@ -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)
}

View File

@ -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"))

View File

@ -1,4 +1,3 @@
// course
package models
type Course struct {

View File

@ -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

View File

@ -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 {

View File

@ -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)

View File

@ -1,4 +1,3 @@
// raum
package models
type Room struct {

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -1,4 +1,3 @@
// raum
package repositories
import (

View File

@ -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)
}