sprechstunden-go/repositories/request.go
2022-09-24 15:01:33 +02:00

174 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// request
package repositories
import (
"bytes"
"crypto/rand"
"database/sql"
"errors"
"fmt"
"log"
"math/big"
"net/smtp"
"officeHours/config"
"officeHours/models"
"officeHours/templating"
)
type RequestRepo struct {
db *sql.DB
officeHourRepo models.OfficeHourRepository
config config.Config
}
func NewRequestRepo(db *sql.DB, officeHourRepo models.OfficeHourRepository, config config.Config) *RequestRepo {
return &RequestRepo{
db: db,
officeHourRepo: officeHourRepo,
config: config,
}
}
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 {
err = fmt.Errorf("Error scanning request row: %w", err)
log.Println(err.Error())
return requests, err
}
request.OfficeHour, err = r.officeHourRepo.FindById(officeHourId)
if 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 && !errors.Is(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, r.sendConfirmationMail(request)
}
}
secret, err := r.newSecret()
if err != nil {
return 0, err
}
request := models.Request{Id: 0, OfficeHour: officeHour, Action: action, Secret: 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, r.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 officeHour=?", request.OfficeHour.Id)
case models.RequestDelete:
err = r.officeHourRepo.Delete(request.OfficeHour)
r.db.Exec("DELETE FROM request WHERE officeHour=?", request.OfficeHour.Id)
default:
_, err = r.db.Exec("DELETE FROM request WHERE id=?", request.Id)
}
return err
}
func (r *RequestRepo) newSecret() (string, error) {
var err error
var secret string
// find unused secret
for !errors.Is(err, sql.ErrNoRows) {
secret = randomString(r.config.Request.SecretLength)
_, err = r.FindBySecret(secret)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return "", err
}
}
return secret, nil
}
func (r *RequestRepo) sendConfirmationMail(request models.Request) error {
var message bytes.Buffer
var data = struct {
Config config.Config
Request models.Request
}{r.config, request}
err := templating.WriteTemplate(&message, "confirmationMail", data)
if err != nil {
err = fmt.Errorf("Error parsing confirmation Mail: %w", err)
log.Println(err.Error())
return err
}
switch r.config.Mailer.Type {
case "Stdout":
fmt.Println(message.String())
case "Smtp":
to := []string{request.OfficeHour.Tutor.Email}
var auth smtp.Auth
if r.config.Mailer.SmtpUseAuth {
auth = smtp.PlainAuth(r.config.Mailer.SmtpIdentity, r.config.Mailer.FromAddress, r.config.Mailer.SmtpPassword, r.config.Mailer.SmtpHost)
}
err = smtp.SendMail(fmt.Sprintf("%s:%d", r.config.Mailer.SmtpHost, r.config.Mailer.SmtpPort), auth, string(r.config.Mailer.FromName), to, message.Bytes())
if err != nil {
err = fmt.Errorf("Error sending mail by smtp: %w", err)
log.Println(err.Error())
}
return err
}
return nil
}
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, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
if err != nil {
log.Printf("Error getting random position in randomString(n int): %s", err.Error())
}
s[i] = letters[position.Int64()]
}
return string(s)
}