Split mail functionality in a separate package, improve multiuser features
This commit is contained in:
parent
b2348b2df9
commit
768c8b84ca
10 changed files with 181 additions and 47 deletions
79
mail/mail.go
Normal file
79
mail/mail.go
Normal 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
34
orm/creator.go
Normal 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
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in a new issue