Notification endpoint (for sending emails from the CRM)
This commit is contained in:
@@ -143,6 +143,8 @@ mocks: $(GOMOCK)
|
||||
rm -f */*/*_mock_test.go
|
||||
|
||||
# See https://github.com/golang/mock for details
|
||||
$(MOCKGEN) -package service -source crm/service/notification.go -destination crm/service/notification_mock_test.go
|
||||
|
||||
$(MOCKGEN) -package service -source sam/service/attachment.go -destination sam/service/attachment_mock_test.go
|
||||
$(MOCKGEN) -package service -source sam/service/channel.go -destination sam/service/channel_mock_test.go
|
||||
$(MOCKGEN) -package service -source sam/service/message.go -destination sam/service/message_mock_test.go
|
||||
|
||||
@@ -751,5 +751,37 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Notifications",
|
||||
"description": "CRM Notifications",
|
||||
"package": "crm",
|
||||
"entrypoint": "notification",
|
||||
"path": "/notification",
|
||||
"authentication": [],
|
||||
"struct": [
|
||||
{
|
||||
"imports": [
|
||||
"sqlxTypes github.com/jmoiron/sqlx/types"
|
||||
]
|
||||
}
|
||||
],
|
||||
"apis": [
|
||||
{
|
||||
"name": "email/send",
|
||||
"method": "POST",
|
||||
"title": "Send email from the CRM",
|
||||
"path": "/email",
|
||||
"parameters": {
|
||||
"post": [
|
||||
{ "name": "to", "type": "[]string", "required": true, "title": "Email addresses or Crust user IDs" },
|
||||
{ "name": "cc", "type": "[]string", "required": false, "title": "Email addresses or Crust user IDs" },
|
||||
{ "name": "replyTo", "type": "string", "required": false, "title": "Crust user ID or email address in reply-to field" },
|
||||
{ "name": "subject ", "type": "string", "required": false, "title": "Email subject" },
|
||||
{ "name": "content", "type": "sqlxTypes.JSONText", "required": true, "title": "Message content" }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"Title": "Notifications",
|
||||
"Description": "CRM Notifications",
|
||||
"Package": "crm",
|
||||
"Interface": "Notification",
|
||||
"Struct": [
|
||||
{
|
||||
"imports": [
|
||||
"sqlxTypes github.com/jmoiron/sqlx/types"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Parameters": null,
|
||||
"Protocol": "",
|
||||
"Authentication": [],
|
||||
"Path": "/notification",
|
||||
"APIs": [
|
||||
{
|
||||
"Name": "email/send",
|
||||
"Method": "POST",
|
||||
"Title": "Send email from the CRM",
|
||||
"Path": "/email",
|
||||
"Parameters": {
|
||||
"post": [
|
||||
{
|
||||
"name": "to",
|
||||
"required": true,
|
||||
"title": "Email addresses or Crust user IDs",
|
||||
"type": "[]string"
|
||||
},
|
||||
{
|
||||
"name": "cc",
|
||||
"required": false,
|
||||
"title": "Email addresses or Crust user IDs",
|
||||
"type": "[]string"
|
||||
},
|
||||
{
|
||||
"name": "replyTo",
|
||||
"required": false,
|
||||
"title": "Crust user ID or email address in reply-to field",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "subject ",
|
||||
"required": false,
|
||||
"title": "Email subject",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "content",
|
||||
"required": true,
|
||||
"title": "Message content",
|
||||
"type": "sqlxTypes.JSONText"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package handlers
|
||||
|
||||
/*
|
||||
Hello! This file is auto-generated from `docs/src/spec.json`.
|
||||
|
||||
For development:
|
||||
In order to update the generated files, edit this file under the location,
|
||||
add your struct fields, imports, API definitions and whatever you want, and:
|
||||
|
||||
1. run [spec](https://github.com/titpetric/spec) in the same folder,
|
||||
2. run `./_gen.php` in this folder.
|
||||
|
||||
You may edit `notification.go`, `notification.util.go` or `notification_test.go` to
|
||||
implement your API calls, helper functions and tests. The file `notification.go`
|
||||
is only generated the first time, and will not be overwritten if it exists.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-chi/chi"
|
||||
"net/http"
|
||||
|
||||
"github.com/titpetric/factory/resputil"
|
||||
|
||||
"github.com/crusttech/crust/crm/rest/request"
|
||||
)
|
||||
|
||||
// Internal API interface
|
||||
type NotificationAPI interface {
|
||||
EmailSend(context.Context, *request.NotificationEmailSend) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
type Notification struct {
|
||||
EmailSend func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
func NewNotification(nh NotificationAPI) *Notification {
|
||||
return &Notification{
|
||||
EmailSend: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewNotificationEmailSend()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return nh.EmailSend(r.Context(), params)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (nh *Notification) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Route("/notification", func(r chi.Router) {
|
||||
r.Post("/email", nh.EmailSend)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/crusttech/crust/crm/rest/request"
|
||||
"github.com/crusttech/crust/crm/service"
|
||||
"github.com/crusttech/crust/internal/mail"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ = errors.Wrap
|
||||
|
||||
type (
|
||||
Notification struct {
|
||||
notification service.NotificationService
|
||||
}
|
||||
|
||||
contentPayload struct {
|
||||
Plain string `json:"plain"`
|
||||
Html string `json:"html"`
|
||||
}
|
||||
)
|
||||
|
||||
func (Notification) New() *Notification {
|
||||
return &Notification{
|
||||
notification: service.DefaultNotification,
|
||||
}
|
||||
}
|
||||
|
||||
// EmailSend assembles Email Message and pushes message to notification service
|
||||
func (ctrl *Notification) EmailSend(ctx context.Context, r *request.NotificationEmailSend) (interface{}, error) {
|
||||
ntf := ctrl.notification.With(ctx)
|
||||
|
||||
msg := mail.New()
|
||||
if err := ntf.AttachEmailRecipients(msg, "To", r.To...); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := ntf.AttachEmailRecipients(msg, "Cc", r.Cc...); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var cp = contentPayload{}
|
||||
if err := r.Content.Unmarshal(&cp); err != nil {
|
||||
return false, errors.Wrap(err, "Could not unmarshal content")
|
||||
} else {
|
||||
if len(cp.Html) > 0 {
|
||||
msg.SetBody("text/html", cp.Html)
|
||||
|
||||
}
|
||||
|
||||
if len(cp.Plain) > 0 {
|
||||
msg.SetBody("text/plain", cp.Plain)
|
||||
}
|
||||
}
|
||||
|
||||
msg.SetHeader("Subject", r.Subject)
|
||||
|
||||
if err := ctrl.notification.With(ctx).SendEmail(msg); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package request
|
||||
|
||||
/*
|
||||
Hello! This file is auto-generated from `docs/src/spec.json`.
|
||||
|
||||
For development:
|
||||
In order to update the generated files, edit this file under the location,
|
||||
add your struct fields, imports, API definitions and whatever you want, and:
|
||||
|
||||
1. run [spec](https://github.com/titpetric/spec) in the same folder,
|
||||
2. run `./_gen.php` in this folder.
|
||||
|
||||
You may edit `notification.go`, `notification.util.go` or `notification_test.go` to
|
||||
implement your API calls, helper functions and tests. The file `notification.go`
|
||||
is only generated the first time, and will not be overwritten if it exists.
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
sqlxTypes "github.com/jmoiron/sqlx/types"
|
||||
)
|
||||
|
||||
var _ = chi.URLParam
|
||||
var _ = multipart.FileHeader{}
|
||||
|
||||
// Notification email/send request parameters
|
||||
type NotificationEmailSend struct {
|
||||
To []string
|
||||
Cc []string
|
||||
ReplyTo string
|
||||
Subject string
|
||||
Content sqlxTypes.JSONText
|
||||
}
|
||||
|
||||
func NewNotificationEmailSend() *NotificationEmailSend {
|
||||
return &NotificationEmailSend{}
|
||||
}
|
||||
|
||||
func (n *NotificationEmailSend) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(n)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
if val, ok := post["replyTo"]; ok {
|
||||
|
||||
n.ReplyTo = val
|
||||
}
|
||||
if val, ok := post["subject "]; ok {
|
||||
|
||||
n.Subject = val
|
||||
}
|
||||
if val, ok := post["content"]; ok {
|
||||
|
||||
if n.Content, err = parseJSONTextWithErr(val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewNotificationEmailSend()
|
||||
+6
-4
@@ -9,10 +9,11 @@ import (
|
||||
|
||||
func MountRoutes(jwtAuth auth.TokenEncoder) func(chi.Router) {
|
||||
var (
|
||||
module = Module{}.New()
|
||||
page = Page{}.New()
|
||||
chart = Chart{}.New()
|
||||
trigger = Trigger{}.New()
|
||||
module = Module{}.New()
|
||||
page = Page{}.New()
|
||||
chart = Chart{}.New()
|
||||
trigger = Trigger{}.New()
|
||||
notification = Notification{}.New()
|
||||
)
|
||||
|
||||
// Initialize handers & controllers.
|
||||
@@ -25,6 +26,7 @@ func MountRoutes(jwtAuth auth.TokenEncoder) func(chi.Router) {
|
||||
handlers.NewModule(module).MountRoutes(r)
|
||||
handlers.NewChart(chart).MountRoutes(r)
|
||||
handlers.NewTrigger(trigger).MountRoutes(r)
|
||||
handlers.NewNotification(notification).MountRoutes(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
gomail "gopkg.in/mail.v2"
|
||||
|
||||
"github.com/crusttech/crust/internal/mail"
|
||||
systemService "github.com/crusttech/crust/system/service"
|
||||
systemTypes "github.com/crusttech/crust/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
notification struct {
|
||||
ctx context.Context
|
||||
|
||||
userSvc notificationUserService
|
||||
}
|
||||
|
||||
NotificationService interface {
|
||||
With(ctx context.Context) NotificationService
|
||||
SendEmail(message *gomail.Message) error
|
||||
AttachEmailRecipients(message *gomail.Message, field string, recipients ...string) error
|
||||
}
|
||||
|
||||
notificationUserService interface {
|
||||
With(ctx context.Context) systemService.UserService
|
||||
FindByID(userID uint64) (*systemTypes.User, error)
|
||||
}
|
||||
)
|
||||
|
||||
func Notification() NotificationService {
|
||||
return (¬ification{
|
||||
userSvc: systemService.DefaultUser,
|
||||
}).With(context.Background())
|
||||
}
|
||||
|
||||
func (s *notification) With(ctx context.Context) NotificationService {
|
||||
return ¬ification{
|
||||
ctx: ctx,
|
||||
|
||||
userSvc: s.userSvc.With(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *notification) SendEmail(message *gomail.Message) error {
|
||||
return mail.Send(message)
|
||||
}
|
||||
|
||||
// AttachEmailRecipients validates, resolves, formats andd attaches set of recipients to message
|
||||
//
|
||||
// Supports 3 input formats:
|
||||
// - <valid email>
|
||||
// - <valid email><space><name...>
|
||||
// - <userID>
|
||||
// Last one is then translated into valid email + name (when/if possible)
|
||||
func (s *notification) AttachEmailRecipients(message *gomail.Message, field string, recipients ...string) (err error) {
|
||||
var (
|
||||
email string
|
||||
name string
|
||||
)
|
||||
|
||||
if len(recipients) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if recipients, err = s.expandUserRefs(s.userSvc.With(s.ctx), recipients); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for r, rcpt := range recipients {
|
||||
name, email = "", ""
|
||||
rcpt = strings.TrimSpace(rcpt)
|
||||
|
||||
// First, get userID off the table
|
||||
if spaceAt := strings.Index(rcpt, " "); spaceAt > -1 {
|
||||
email, name = rcpt[:spaceAt], strings.TrimSpace(rcpt[spaceAt+1:])
|
||||
} else {
|
||||
email = rcpt
|
||||
}
|
||||
|
||||
// Validate email here
|
||||
if !mail.IsValidAddress(email) {
|
||||
return errors.New("Invalid recipient email format")
|
||||
}
|
||||
|
||||
recipients[r] = message.FormatAddress(email, name)
|
||||
}
|
||||
|
||||
message.SetHeader(field, recipients...)
|
||||
return
|
||||
}
|
||||
|
||||
// Expands references to users (strings as numeric uint64)
|
||||
//
|
||||
// This func is extracted to make testing/mocking mocking
|
||||
func (s *notification) expandUserRefs(usrLookup notificationUserService, recipients []string) ([]string, error) {
|
||||
for r, rcpt := range recipients {
|
||||
// First, get userID off the table
|
||||
if userID, _ := strconv.ParseUint(rcpt, 10, 64); userID > 0 {
|
||||
if user, err := usrLookup.FindByID(userID); err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid recipient %s", userID)
|
||||
} else {
|
||||
recipients[r] = user.Email + " " + user.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return recipients, nil
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: crm/service/notification.go
|
||||
|
||||
// Package service is a generated GoMock package.
|
||||
package service
|
||||
|
||||
import (
|
||||
context "context"
|
||||
service0 "github.com/crusttech/crust/system/service"
|
||||
types "github.com/crusttech/crust/system/types"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
mail_v2 "gopkg.in/mail.v2"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockNotificationService is a mock of NotificationService interface
|
||||
type MockNotificationService struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockNotificationServiceMockRecorder
|
||||
}
|
||||
|
||||
// MockNotificationServiceMockRecorder is the mock recorder for MockNotificationService
|
||||
type MockNotificationServiceMockRecorder struct {
|
||||
mock *MockNotificationService
|
||||
}
|
||||
|
||||
// NewMockNotificationService creates a new mock instance
|
||||
func NewMockNotificationService(ctrl *gomock.Controller) *MockNotificationService {
|
||||
mock := &MockNotificationService{ctrl: ctrl}
|
||||
mock.recorder = &MockNotificationServiceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockNotificationService) EXPECT() *MockNotificationServiceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// With mocks base method
|
||||
func (m *MockNotificationService) With(ctx context.Context) NotificationService {
|
||||
ret := m.ctrl.Call(m, "With", ctx)
|
||||
ret0, _ := ret[0].(NotificationService)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// With indicates an expected call of With
|
||||
func (mr *MockNotificationServiceMockRecorder) With(ctx interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "With", reflect.TypeOf((*MockNotificationService)(nil).With), ctx)
|
||||
}
|
||||
|
||||
// SendEmail mocks base method
|
||||
func (m *MockNotificationService) SendEmail(message *mail_v2.Message) error {
|
||||
ret := m.ctrl.Call(m, "SendEmail", message)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SendEmail indicates an expected call of SendEmail
|
||||
func (mr *MockNotificationServiceMockRecorder) SendEmail(message interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendEmail", reflect.TypeOf((*MockNotificationService)(nil).SendEmail), message)
|
||||
}
|
||||
|
||||
// AttachEmailRecipients mocks base method
|
||||
func (m *MockNotificationService) AttachEmailRecipients(message *mail_v2.Message, field string, recipients ...string) error {
|
||||
varargs := []interface{}{message, field}
|
||||
for _, a := range recipients {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "AttachEmailRecipients", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AttachEmailRecipients indicates an expected call of AttachEmailRecipients
|
||||
func (mr *MockNotificationServiceMockRecorder) AttachEmailRecipients(message, field interface{}, recipients ...interface{}) *gomock.Call {
|
||||
varargs := append([]interface{}{message, field}, recipients...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AttachEmailRecipients", reflect.TypeOf((*MockNotificationService)(nil).AttachEmailRecipients), varargs...)
|
||||
}
|
||||
|
||||
// MocknotificationUserService is a mock of notificationUserService interface
|
||||
type MocknotificationUserService struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MocknotificationUserServiceMockRecorder
|
||||
}
|
||||
|
||||
// MocknotificationUserServiceMockRecorder is the mock recorder for MocknotificationUserService
|
||||
type MocknotificationUserServiceMockRecorder struct {
|
||||
mock *MocknotificationUserService
|
||||
}
|
||||
|
||||
// NewMocknotificationUserService creates a new mock instance
|
||||
func NewMocknotificationUserService(ctrl *gomock.Controller) *MocknotificationUserService {
|
||||
mock := &MocknotificationUserService{ctrl: ctrl}
|
||||
mock.recorder = &MocknotificationUserServiceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MocknotificationUserService) EXPECT() *MocknotificationUserServiceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// With mocks base method
|
||||
func (m *MocknotificationUserService) With(ctx context.Context) service0.UserService {
|
||||
ret := m.ctrl.Call(m, "With", ctx)
|
||||
ret0, _ := ret[0].(service0.UserService)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// With indicates an expected call of With
|
||||
func (mr *MocknotificationUserServiceMockRecorder) With(ctx interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "With", reflect.TypeOf((*MocknotificationUserService)(nil).With), ctx)
|
||||
}
|
||||
|
||||
// FindByID mocks base method
|
||||
func (m *MocknotificationUserService) FindByID(userID uint64) (*types.User, error) {
|
||||
ret := m.ctrl.Call(m, "FindByID", userID)
|
||||
ret0, _ := ret[0].(*types.User)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FindByID indicates an expected call of FindByID
|
||||
func (mr *MocknotificationUserServiceMockRecorder) FindByID(userID interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByID", reflect.TypeOf((*MocknotificationUserService)(nil).FindByID), userID)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
gomail "gopkg.in/mail.v2"
|
||||
|
||||
"github.com/crusttech/crust/internal/test"
|
||||
"github.com/crusttech/crust/system/types"
|
||||
)
|
||||
|
||||
func TestUserRefExpanding(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
ntf := ¬ification{}
|
||||
|
||||
// msg := &gomail.Message{}
|
||||
usr := &types.User{ID: 72932592256548967, Email: "user@mock.ed", Name: "Mocked User"}
|
||||
|
||||
usrSvc := NewMocknotificationUserService(mockCtrl)
|
||||
usrSvc.EXPECT().FindByID(usr.ID).Times(1).Return(usr, nil)
|
||||
ntf.userSvc = usrSvc
|
||||
|
||||
input := []string{"sample@domain.tld", "sample@domain.tld Name", "72932592256548967"}
|
||||
rcpts, err := ntf.expandUserRefs(usrSvc, input)
|
||||
test.ErrNil(t, err, "expandUserRefs returned an error: %v")
|
||||
test.Assert(t, len(rcpts) == len(input), "Expecting %d headers, got %d", len(input), len(rcpts))
|
||||
test.Assert(t, rcpts[2] == usr.Email+" "+usr.Name, "Expecting %d headers, got %d", len(input), len(rcpts))
|
||||
}
|
||||
|
||||
func TestAttachEmailRecipients(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
ntf := ¬ification{}
|
||||
|
||||
usrSvc := NewMocknotificationUserService(mockCtrl)
|
||||
usrSvc.EXPECT().With(gomock.Any())
|
||||
ntf.userSvc = usrSvc
|
||||
|
||||
msg := gomail.NewMessage()
|
||||
|
||||
input := []string{"sample@domain.tld", "sample2@domain.tld First Name"}
|
||||
err := ntf.AttachEmailRecipients(msg, "To", input...)
|
||||
to := msg.GetHeader("To")
|
||||
|
||||
test.ErrNil(t, err, "AttachEmailRecipients returned an error: %v")
|
||||
|
||||
test.Assert(t, len(input) == len(to), "Expecting %d headers, got %d", len(input), len(to))
|
||||
|
||||
test.Assert(t, to[0] == "sample@domain.tld", "Expecting address to match, got %v", to[0])
|
||||
|
||||
test.Assert(t, to[1] == "\"First Name\" <sample2@domain.tld>", "Expecting address to match, got %v", to[1])
|
||||
}
|
||||
@@ -5,12 +5,13 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
o sync.Once
|
||||
DefaultRecord RecordService
|
||||
DefaultModule ModuleService
|
||||
DefaultTrigger TriggerService
|
||||
DefaultChart ChartService
|
||||
DefaultPage PageService
|
||||
o sync.Once
|
||||
DefaultRecord RecordService
|
||||
DefaultModule ModuleService
|
||||
DefaultTrigger TriggerService
|
||||
DefaultChart ChartService
|
||||
DefaultPage PageService
|
||||
DefaultNotification NotificationService
|
||||
)
|
||||
|
||||
func Init() {
|
||||
@@ -20,5 +21,6 @@ func Init() {
|
||||
DefaultTrigger = Trigger()
|
||||
DefaultPage = Page()
|
||||
DefaultChart = Chart()
|
||||
DefaultNotification = Notification()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -253,6 +253,31 @@ CRM module definitions
|
||||
|
||||
|
||||
|
||||
# Notifications
|
||||
|
||||
CRM Notifications
|
||||
|
||||
## Send email from the CRM
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/notification/email` | HTTP/S | POST | |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| to | []string | POST | Email addresses or Crust user IDs | N/A | YES |
|
||||
| cc | []string | POST | Email addresses or Crust user IDs | N/A | NO |
|
||||
| replyTo | string | POST | Crust user ID or email address in reply-to field | N/A | NO |
|
||||
| subject | string | POST | Email subject | N/A | NO |
|
||||
| content | sqlxTypes.JSONText | POST | Message content | N/A | YES |
|
||||
|
||||
|
||||
|
||||
|
||||
# Pages
|
||||
|
||||
CRM module pages
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: internal/mail/mail.go
|
||||
|
||||
// Package service is a generated GoMock package.
|
||||
// Package mail is a generated GoMock package.
|
||||
package mail
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
mail_v2 "gopkg.in/mail.v2"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockDialer is a mock of Dialer interface
|
||||
|
||||
Reference in New Issue
Block a user