sprechstunden-go/repositories/request.go

142 lines
4.1 KiB
Go
Raw Normal View History

// request
package repositories
import (
"bytes"
"crypto/rand"
"database/sql"
"html/template"
"math/big"
"net/smtp"
"sprechstundentool/models"
)
type RequestRepo struct {
db *sql.DB
officeHourRepo models.OfficeHourRepository
}
func NewRequestRepo(db *sql.DB, officeHourRepo models.OfficeHourRepository) *RequestRepo {
return &RequestRepo{
db: db,
officeHourRepo: officeHourRepo,
}
}
func (r *RequestRepo) FindBySecret(secret string) (models.Request, error) {
// This query is not safe against timing sidechannel attacks I don't care.
row := r.db.QueryRow("SELECT * FROM request WHERE secret=?", secret)
var request models.Request
var officeHourId int
err := row.Scan(&request.Id, &officeHourId, &request.Action, &request.Secret)
if err != nil {
return models.Request{}, err
}
request.OfficeHour, err = r.officeHourRepo.FindById(officeHourId)
return request, err
}
func (r *RequestRepo) FindByOfficeHour(officeHour models.OfficeHour) ([]models.Request, error) {
rows, err := r.db.Query("SELECT * FROM request WHERE officeHour=?", officeHour.Id)
if err != nil {
return nil, err
}
defer rows.Close()
var requests []models.Request
for rows.Next() {
var request models.Request
var officeHourId int
if err := rows.Scan(&request.Id, &officeHourId, &request.Action, &request.Secret); err != nil {
return requests, err
}
requests = append(requests, request)
}
return requests, nil
}
func (r *RequestRepo) Add(officeHour models.OfficeHour, action int) (int, error) {
existents, err := r.FindByOfficeHour(officeHour)
if err != nil && err != sql.ErrNoRows {
return 0, err
}
/* Resend confirmation mail if identical request exists,
* but don't insert new request into database.
*/
for _, request := range existents {
if request.Action == action { // already covered by selection && request.OfficeHour == officeHour {
return request.Id, sendConfirmationMail(request)
}
}
secret, err := r.newSecret()
if err != nil {
return 0, err
}
request := models.Request{0, officeHour, action, secret}
_, err = r.db.Exec("INSERT INTO `request` (officeHour, action, secret) VALUES (?,?,?)", officeHour.Id, action, secret)
if err != nil {
return 0, err
}
request, err = r.FindBySecret(secret)
if err != nil {
return request.Id, err
}
return request.Id, sendConfirmationMail(request)
}
func (r *RequestRepo) Execute(request models.Request) error {
var err error
switch request.Action {
case models.RequestActivate:
_, err = r.db.Exec("UPDATE officeHour SET active=true WHERE id=?", request.OfficeHour.Id)
r.db.Exec("DELETE FROM request WHERE id=?", request.Id)
case models.RequestDelete:
_, err = r.db.Exec("DELETE FROM officeHour WHERE id=?", request.OfficeHour.Id)
r.db.Exec("DELETE FROM request WHERE id=?", request.Id)
default:
r.db.Exec("DELETE FROM request WHERE id=?", request.Id)
}
return err
}
func (r *RequestRepo) newSecret() (string, error) {
secret := randomString(models.SecretLength)
_, err := r.FindBySecret(secret)
if err != nil && err != sql.ErrNoRows {
return "", err
}
// find unused secret
for err != sql.ErrNoRows {
secret = randomString(models.SecretLength)
_, err = r.FindBySecret(secret)
}
return secret, nil
}
func sendConfirmationMail(request models.Request) error {
to := []string{request.OfficeHour.Tutor.Email}
tmpl, err := template.New("confirmationMail").Funcs(template.FuncMap{"DayName": models.DayName}).ParseFiles("templates/confirmationMail")
var message bytes.Buffer
err = tmpl.Execute(&message, request)
if err != nil {
return err
}
err = smtp.SendMail("192.168.0.24:25", nil, "Mathebau Sprechstunden <sprechstunden@mathebau.de>", to, message.Bytes())
return err
}
func randomString(n int) string {
// []byte would be faster and also work, but []rune keeps working for UTF8 characters
// if someone would like them.
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
s := make([]rune, n)
for i := range s {
position, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
s[i] = letters[position.Int64()]
}
return string(s)
}