Split mail functionality in a separate package, improve multiuser features

This commit is contained in:
Andrea Fazzi 2019-12-06 10:59:00 +01:00
parent b2348b2df9
commit 768c8b84ca
10 changed files with 181 additions and 47 deletions

79
mail/mail.go Normal file
View file

@ -0,0 +1,79 @@
package mail
import (
"bytes"
"crypto/tls"
"text/template"
"git.andreafazzi.eu/andrea/oef/config"
"gopkg.in/gomail.v2"
)
type Subscriber interface {
NameForMail() string
Username() string
Password() string
To() string
}
var (
mail = `
Spettabile {{.NameForMail}},
grazie per l'interesse manifestato per le Olimpiadi di Economia e Finanza.
Di seguito riportiamo le credenziali di accesso tramite le quali potrà gestire le iscrizioni dei suoi studenti alla competizione (Fase Regionale).
username: {{.Username}}
password: {{.Password}}
Per accedere alla pagina di login occorrerà seguire questo link
https://iscrizioni.olimpiadi-economiaefinanza.it/
ed inserire le credenziali riportate sopra (si consiglia di effettuare un copia/incolla).
Cordialmente,
Lo Staff delle OEF 2020.
`
subject = "[OEF2020] - Credenziali di accesso della scuola"
mailTpl *template.Template
)
func init() {
mailTpl = template.Must(template.New("subscription_mail").Parse(mail))
}
func SendSubscriptionMail(rcv Subscriber) error {
var body bytes.Buffer
m := gomail.NewMessage()
m.SetHeader("Subject", subject)
m.SetHeader("From", config.Config.Smtp.From)
m.SetHeader("To", rcv.To())
m.SetHeader("Bcc", config.Config.Smtp.Bcc)
err := mailTpl.Execute(&body, rcv)
if err != nil {
return err
}
m.SetBody("text/plain", body.String())
dialer := gomail.NewDialer(
config.Config.Smtp.Host,
config.Config.Smtp.Port,
config.Config.Smtp.Username,
config.Config.Smtp.Password,
)
dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
if err := dialer.DialAndSend(m); err != nil {
return err
}
return nil
}

34
orm/creator.go Normal file
View file

@ -0,0 +1,34 @@
package orm
import (
"net/http"
"github.com/dgrijalva/jwt-go"
)
type Creator struct {
CreatorID string
CreatorRole string
CreatorIP string
}
func NewCreator(r *http.Request) *Creator {
var claims jwt.MapClaims
if r.Context().Value("user") != nil {
claims = r.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)
}
return &Creator{
CreatorID: claims["user_id"].(string),
CreatorRole: claims["role"].(string),
CreatorIP: r.RemoteAddr,
}
}
func (c *Creator) CreatedBy() (*User, error) {
var user User
if err := DB().First(&user, c.CreatorID).Error; err != nil {
return nil, err
}
return &user, nil
}

View file

@ -15,6 +15,8 @@ import (
type Participant struct { type Participant struct {
gorm.Model gorm.Model
*Creator
UserID uint UserID uint
Firstname string Firstname string
@ -143,6 +145,8 @@ func (model *Participant) Create(args map[string]string, w http.ResponseWriter,
return nil, err return nil, err
} }
participant.Creator = NewCreator(r)
participant, err = CreateParticipant(participant) participant, err = CreateParticipant(participant)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -1,21 +1,21 @@
package orm package orm
import ( import (
"crypto/tls"
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
"time" "time"
"git.andreafazzi.eu/andrea/oef/config" "git.andreafazzi.eu/andrea/oef/mail"
"git.andreafazzi.eu/andrea/oef/renderer" "git.andreafazzi.eu/andrea/oef/renderer"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"gopkg.in/gomail.v2"
) )
type School struct { type School struct {
gorm.Model gorm.Model
*Creator
Name string Name string
Email string Email string
Code string Code string
@ -33,13 +33,25 @@ func (model *School) String() string {
return fmt.Sprintf("%s (%s)", model.Code, model.Name) return fmt.Sprintf("%s (%s)", model.Code, model.Name)
} }
func (model *School) username() string { func (model *School) NameForMail() string {
return fmt.Sprintf("%s (%s)", model.Name, model.Code)
}
func (model *School) Username() string {
return strings.ToUpper(model.Code) return strings.ToUpper(model.Code)
} }
func (model *School) Password() string {
return model.User.Password
}
func (model *School) To() string {
return model.Email
}
func (model *School) exists() (*User, error) { func (model *School) exists() (*User, error) {
var user User var user User
if err := DB().First(&user, &User{Username: model.username()}).Error; err != nil && err != gorm.ErrRecordNotFound { if err := DB().First(&user, &User{Username: model.Username()}).Error; err != nil && err != gorm.ErrRecordNotFound {
return nil, err return nil, err
} else if err == gorm.ErrRecordNotFound { } else if err == gorm.ErrRecordNotFound {
return nil, nil return nil, nil
@ -50,7 +62,7 @@ func (model *School) exists() (*User, error) {
func (model *School) BeforeSave(tx *gorm.DB) error { func (model *School) BeforeSave(tx *gorm.DB) error {
var user User var user User
if err := tx.FirstOrCreate(&user, &User{ if err := tx.FirstOrCreate(&user, &User{
Username: model.username(), Username: model.Username(),
Role: "school", Role: "school",
}).Error; err != nil { }).Error; err != nil {
return err return err
@ -67,21 +79,10 @@ func (model *School) AfterDelete(tx *gorm.DB) error {
} }
func (model *School) AfterCreate(tx *gorm.DB) error { func (model *School) AfterCreate(tx *gorm.DB) error {
m := gomail.NewMessage() if err := tx.Preload("User").First(model).Error; err != nil {
m.SetHeader("From", config.Config.Smtp.From) return err
m.SetHeader("To", model.Email) }
m.SetHeader("Bcc", config.Config.Smtp.Bcc) if err := mail.SendSubscriptionMail(model); err != nil {
m.SetBody("text/plain", "SMTP test message.")
dialer := gomail.NewDialer(
config.Config.Smtp.Host,
config.Config.Smtp.Port,
config.Config.Smtp.Username,
config.Config.Smtp.Password,
)
dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
if err := dialer.DialAndSend(m); err != nil {
return err return err
} }
@ -117,6 +118,8 @@ func (model *School) Create(args map[string]string, w http.ResponseWriter, r *ht
return nil, err return nil, err
} }
school.Creator = NewCreator(r)
school, err = CreateSchool(school) school, err = CreateSchool(school)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -45,9 +45,6 @@ func (model *User) BeforeSave(tx *gorm.DB) error {
func (model *User) Create(args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) { func (model *User) Create(args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) {
if r.Method == "GET" { if r.Method == "GET" {
user := new(User) user := new(User)
// if err := DB().Find(&user.AllContests).Error; err != nil {
// return nil, err
// }
return user, nil return user, nil
} else { } else {
user := new(User) user := new(User)

View file

@ -4,7 +4,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"html/template" "html/template"
"log"
"net/url" "net/url"
"reflect" "reflect"
"strings" "strings"
@ -25,6 +24,7 @@ var (
"convertDate": convertDate, "convertDate": convertDate,
"convertTime": convertTime, "convertTime": convertTime,
"prettyDate": prettyDate, "prettyDate": prettyDate,
"prettyTime": prettyTime,
"modelPath": modelPath, "modelPath": modelPath,
"dict": dict, "dict": dict,
"yaml": yaml, "yaml": yaml,
@ -52,6 +52,7 @@ var (
"isAdmin": isAdmin, "isAdmin": isAdmin,
"isParticipant": isParticipant, "isParticipant": isParticipant,
"isSubscriber": isSubscriber, "isSubscriber": isSubscriber,
"attr": attr,
} }
) )
@ -118,6 +119,10 @@ func html(content string) template.HTML {
return template.HTML(content) return template.HTML(content)
} }
func attr(attr string) template.HTMLAttr {
return template.HTMLAttr(attr)
}
func anchor(text, url string) template.HTML { func anchor(text, url string) template.HTML {
return template.HTML(fmt.Sprintf("<a href=\"%s\">%s</a>", url, text)) return template.HTML(fmt.Sprintf("<a href=\"%s\">%s</a>", url, text))
} }
@ -155,7 +160,6 @@ func yaml(content string) (interface{}, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Println(result)
return result, nil return result, nil
} }
@ -235,6 +239,10 @@ func convertTime(value interface{}) string {
return fmt.Sprintf("%02d:%02d", t.Hour(), t.Minute()) return fmt.Sprintf("%02d:%02d", t.Hour(), t.Minute())
} }
func prettyTime(value interface{}) string {
return convertTime(value)
}
func modelPath(model string, action string, id uint) string { func modelPath(model string, action string, id uint) string {
var q template.URL var q template.URL

View file

@ -5,7 +5,7 @@
name="{{.options.name}}" name="{{.options.name}}"
class="{{if .options.inputClass}}{{.options.inputClass}}{{else}}form-control{{end}}" class="{{if .options.inputClass}}{{.options.inputClass}}{{else}}form-control{{end}}"
id="{{.options.id}}" id="{{.options.id}}"
placeholder="{{.options.placeholder}}" {{if .update}}value="{{.value}}"{{end}} {{if .options.otherAttrs}}{{.options.otherAttrs|html}}{{end}} {{if .options.required}}required{{end}}> placeholder="{{.options.placeholder}}" {{if .update}}value="{{.value}}"{{end}} {{if .options.otherAttrs}}{{.options.otherAttrs|attr}}{{end}} {{if .options.required}}required{{end}}>
{{if .options.help}}<small class="form-text text-muted">{{.options.help}}</small>{{end}} {{if .options.help}}<small class="form-text text-muted">{{.options.help}}</small>{{end}}
</div> </div>
{{end}} {{end}}

View file

@ -6,12 +6,20 @@
{{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Participant") "deletePath" (.Data.ID|delete "Participant")}} {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Participant") "deletePath" (.Data.ID|delete "Participant")}}
<h2 class="karmen-relation-header">Informazioni generali</h2> <h2 class="karmen-relation-header">Informazioni generali</h2>
<p> <dl class="row">
Il nome utente del partecipante è {{if .Data.User}}<strong>{{.Data.User.Username}}</strong>{{end}} <dt class="col-sm-3">Username</dt>
</p> <dd class="col-sm-9">{{.Data.User.Username}}</dd>
<p> <dt class="col-sm-3">Password</dt>
La sua password è {{if .Data.User}}<strong>{{.Data.User.Password}}</strong>{{end}} <dd class="col-sm-9">{{.Data.User.Password}}</dd>
</p> {{/*if $user:=.Data.CreatedBy*/}}
<!-- <dt class="col-sm-3">Creato da</dt> -->
<!-- <dd class="col-sm-9">{{/*$user.Username*/}}</dd> -->
{{/*end*/}}
<dt class="col-sm-3">IP del creatore</dt>
<dd class="col-sm-9">{{.Data.CreatorIP}}</dd>
</dl>
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">

View file

@ -38,7 +38,7 @@
placeholder: "Inserire il codice meccanografico" placeholder: "Inserire il codice meccanografico"
type: "text" type: "text"
inputClass: "form-control uppercase" inputClass: "form-control uppercase"
otherAttrs: "maxlength=10" otherAttrs: "maxlength=10 minlength=10"
required: "true" required: "true"
`}} `}}
{{template "input" dict "options" ($options|yaml) "value" (.Data|field "Code") "update" $update}} {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Code") "update" $update}}

View file

@ -24,21 +24,22 @@
{{template "show_header" dict "title" (.Data|string|trim) "updatePath" (.Data.ID|update "School") "deletePath" (.Data.ID|delete "School")}} {{template "show_header" dict "title" (.Data|string|trim) "updatePath" (.Data.ID|update "School") "deletePath" (.Data.ID|delete "School")}}
<h2 class="karmen-relation-header">Informazioni sulla scuola</h2> <h2 class="karmen-relation-header">Informazioni sulla scuola</h2>
<p>La denominazione della scuola è <strong>{{.Data.Name}}</strong>.</p> <dl class="row">
<p>Il codice meccanografico della scuola è <strong>{{.Data.Code}}</strong>.</p> <dt class="col-sm-3">Denominazione</dt>
<p>La mail istituzionale della scuola è <strong>{{.Data.Email}}</strong>. <dd class="col-sm-9">{{.Data.Name}}</dd>
<p> <dt class="col-sm-3">Codice meccanografico</dt>
<dd class="col-sm-9">{{.Data.Code}}</dd>
<dt class="col-sm-3">Email</dt>
<dd class="col-sm-9">{{.Data.Email}}</dd>
{{if .Data.EmailSentDate}} {{if .Data.EmailSentDate}}
Una mail contenente le credenziali di accesso per l'iscrizione <dt class="col-sm-3">Data invio credenziali</dt>
degli studenti è stata inviata a questo indirizzo in data <dd class="col-sm-9">{{.Data.EmailSentDate|prettyDate}} alle ore {{.Data.EmailSentDate|prettyTime}}</dd>
<strong>{{.Data.EmailSentDate|prettyDate}}</strong> alle
ore <strong>{{.Data.EmailSentDate|convertTime}}</strong>.
{{else}}
A questo indirizzo non è stata inviata ancora nessuna mail.
{{end}} {{end}}
</p> <dt class="col-sm-3">Username</dt>
<p>Il nome utente della scuola è {{if .Data.User}}<strong>{{.Data.User.Username}}</strong>{{end}}</p> <dd class="col-sm-9">{{.Data.User.Username}}</dd>
<p>La sua password è {{if .Data.User}}<strong>{{.Data.User.Password}}</strong>{{end}}</p> <dt class="col-sm-3">Password</dt>
<dd class="col-sm-9">{{.Data.User.Password}}</dd>
</dl>
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">