diff --git a/automation/automation/email_handler.gen.go b/automation/automation/email_handler.gen.go new file mode 100644 index 000000000..babd3d9a3 --- /dev/null +++ b/automation/automation/email_handler.gen.go @@ -0,0 +1,993 @@ +package automation + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// automation/automation/email_handler.yaml + +import ( + "context" + atypes "github.com/cortezaproject/corteza-server/automation/types" + "github.com/cortezaproject/corteza-server/pkg/expr" + "github.com/cortezaproject/corteza-server/pkg/wfexec" + sysTypes "github.com/cortezaproject/corteza-server/system/types" + "io" +) + +var _ wfexec.ExecResponse + +type ( + emailHandlerRegistry interface { + AddFunctions(ff ...*atypes.Function) + Type(ref string) expr.Type + } +) + +func (h emailHandler) register() { + h.reg.AddFunctions( + h.Send(), + h.Message(), + h.SendMessage(), + h.SetSubject(), + h.SetHeaders(), + h.SetHeader(), + h.SetAddress(), + h.Attach(), + h.Embed(), + ) +} + +type ( + emailSendArgs struct { + hasSubject bool + Subject string + + hasReplyTo bool + ReplyTo interface{} + replyToString string + replyToUser *sysTypes.User + + hasFrom bool + From interface{} + fromString string + fromUser *sysTypes.User + + hasTo bool + To interface{} + toString string + toKV map[string]string + toUser *sysTypes.User + + hasCc bool + Cc interface{} + ccString string + ccKV map[string]string + ccUser *sysTypes.User + + hasHtml bool + Html interface{} + htmlString string + htmlStream io.Reader + + hasPlain bool + Plain interface{} + plainString string + plainStream io.Reader + } +) + +func (a emailSendArgs) GetReplyTo() (bool, string, *sysTypes.User) { + return a.hasReplyTo, a.replyToString, a.replyToUser +} + +func (a emailSendArgs) GetFrom() (bool, string, *sysTypes.User) { + return a.hasFrom, a.fromString, a.fromUser +} + +func (a emailSendArgs) GetTo() (bool, string, map[string]string, *sysTypes.User) { + return a.hasTo, a.toString, a.toKV, a.toUser +} + +func (a emailSendArgs) GetCc() (bool, string, map[string]string, *sysTypes.User) { + return a.hasCc, a.ccString, a.ccKV, a.ccUser +} + +func (a emailSendArgs) GetHtml() (bool, string, io.Reader) { + return a.hasHtml, a.htmlString, a.htmlStream +} + +func (a emailSendArgs) GetPlain() (bool, string, io.Reader) { + return a.hasPlain, a.plainString, a.plainStream +} + +// Send function Sends email message with basic parameters +// +// expects implementation of send function: +// func (h emailHandler) send(ctx context.Context, args *emailSendArgs) (err error) { +// return +// } +func (h emailHandler) Send() *atypes.Function { + return &atypes.Function{ + Ref: "emailSend", + Kind: "function", + Labels: map[string]string(nil), + Meta: &atypes.FunctionMeta{ + Short: "Sends email message with basic parameters", + }, + + Parameters: []*atypes.Param{ + { + Name: "subject", + Types: []string{"String"}, + Meta: &atypes.ParamMeta{ + Label: "Subject", + }, + }, + { + Name: "replyTo", + Types: []string{"String", "User"}, + Meta: &atypes.ParamMeta{ + Label: "Reply to", + }, + }, + { + Name: "from", + Types: []string{"String", "User"}, + Meta: &atypes.ParamMeta{ + Label: "Sender", + }, + }, + { + Name: "to", + Types: []string{"String", "KV", "User"}, + Meta: &atypes.ParamMeta{ + Label: "Recipients", + }, + }, + { + Name: "cc", + Types: []string{"String", "KV", "User"}, + Meta: &atypes.ParamMeta{ + Label: "CC", + }, + }, + { + Name: "html", + Types: []string{"String", "Reader"}, + Meta: &atypes.ParamMeta{ + Label: "HTML message body", + }, + }, + { + Name: "plain", + Types: []string{"String", "Reader"}, + Meta: &atypes.ParamMeta{ + Label: "Plain text message body", + }, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &emailSendArgs{ + hasSubject: in.Has("subject"), + hasReplyTo: in.Has("replyTo"), + hasFrom: in.Has("from"), + hasTo: in.Has("to"), + hasCc: in.Has("cc"), + hasHtml: in.Has("html"), + hasPlain: in.Has("plain"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting ReplyTo argument + if args.hasReplyTo { + aux := expr.Must(expr.Select(in, "replyTo")) + switch aux.Type() { + case h.reg.Type("String").Type(): + args.replyToString = aux.Get().(string) + case h.reg.Type("User").Type(): + args.replyToUser = aux.Get().(*sysTypes.User) + } + } + + // Converting From argument + if args.hasFrom { + aux := expr.Must(expr.Select(in, "from")) + switch aux.Type() { + case h.reg.Type("String").Type(): + args.fromString = aux.Get().(string) + case h.reg.Type("User").Type(): + args.fromUser = aux.Get().(*sysTypes.User) + } + } + + // Converting To argument + if args.hasTo { + aux := expr.Must(expr.Select(in, "to")) + switch aux.Type() { + case h.reg.Type("String").Type(): + args.toString = aux.Get().(string) + case h.reg.Type("KV").Type(): + args.toKV = aux.Get().(map[string]string) + case h.reg.Type("User").Type(): + args.toUser = aux.Get().(*sysTypes.User) + } + } + + // Converting Cc argument + if args.hasCc { + aux := expr.Must(expr.Select(in, "cc")) + switch aux.Type() { + case h.reg.Type("String").Type(): + args.ccString = aux.Get().(string) + case h.reg.Type("KV").Type(): + args.ccKV = aux.Get().(map[string]string) + case h.reg.Type("User").Type(): + args.ccUser = aux.Get().(*sysTypes.User) + } + } + + // Converting Html argument + if args.hasHtml { + aux := expr.Must(expr.Select(in, "html")) + switch aux.Type() { + case h.reg.Type("String").Type(): + args.htmlString = aux.Get().(string) + case h.reg.Type("Reader").Type(): + args.htmlStream = aux.Get().(io.Reader) + } + } + + // Converting Plain argument + if args.hasPlain { + aux := expr.Must(expr.Select(in, "plain")) + switch aux.Type() { + case h.reg.Type("String").Type(): + args.plainString = aux.Get().(string) + case h.reg.Type("Reader").Type(): + args.plainStream = aux.Get().(io.Reader) + } + } + + return out, h.send(ctx, args) + }, + } +} + +type ( + emailMessageArgs struct { + hasSubject bool + Subject string + + hasReplyTo bool + ReplyTo interface{} + replyToString string + replyToUser *sysTypes.User + + hasFrom bool + From interface{} + fromString string + fromUser *sysTypes.User + + hasTo bool + To interface{} + toString string + toKV map[string]string + toUser *sysTypes.User + + hasCc bool + Cc interface{} + ccString string + ccKV map[string]string + ccUser *sysTypes.User + + hasHtml bool + Html interface{} + htmlString string + htmlStream io.Reader + + hasPlain bool + Plain interface{} + plainString string + plainStream io.Reader + } + + emailMessageResults struct { + Message *emailMessage + } +) + +func (a emailMessageArgs) GetReplyTo() (bool, string, *sysTypes.User) { + return a.hasReplyTo, a.replyToString, a.replyToUser +} + +func (a emailMessageArgs) GetFrom() (bool, string, *sysTypes.User) { + return a.hasFrom, a.fromString, a.fromUser +} + +func (a emailMessageArgs) GetTo() (bool, string, map[string]string, *sysTypes.User) { + return a.hasTo, a.toString, a.toKV, a.toUser +} + +func (a emailMessageArgs) GetCc() (bool, string, map[string]string, *sysTypes.User) { + return a.hasCc, a.ccString, a.ccKV, a.ccUser +} + +func (a emailMessageArgs) GetHtml() (bool, string, io.Reader) { + return a.hasHtml, a.htmlString, a.htmlStream +} + +func (a emailMessageArgs) GetPlain() (bool, string, io.Reader) { + return a.hasPlain, a.plainString, a.plainStream +} + +// Message function Constructs new email message +// +// expects implementation of message function: +// func (h emailHandler) message(ctx context.Context, args *emailMessageArgs) (results *emailMessageResults, err error) { +// return +// } +func (h emailHandler) Message() *atypes.Function { + return &atypes.Function{ + Ref: "emailMessage", + Kind: "function", + Labels: map[string]string(nil), + Meta: &atypes.FunctionMeta{ + Short: "Constructs new email message", + }, + + Parameters: []*atypes.Param{ + { + Name: "subject", + Types: []string{"String"}, + Meta: &atypes.ParamMeta{ + Label: "Subject", + }, + }, + { + Name: "replyTo", + Types: []string{"String", "User"}, + Meta: &atypes.ParamMeta{ + Label: "Reply to", + }, + }, + { + Name: "from", + Types: []string{"String", "User"}, + Meta: &atypes.ParamMeta{ + Label: "Sender", + }, + }, + { + Name: "to", + Types: []string{"String", "KV", "User"}, + Meta: &atypes.ParamMeta{ + Label: "Recipients", + }, + }, + { + Name: "cc", + Types: []string{"String", "KV", "User"}, + Meta: &atypes.ParamMeta{ + Label: "CC", + }, + }, + { + Name: "html", + Types: []string{"String", "Reader"}, + Meta: &atypes.ParamMeta{ + Label: "HTML message body", + }, + }, + { + Name: "plain", + Types: []string{"String", "Reader"}, + Meta: &atypes.ParamMeta{ + Label: "Plain text message body", + }, + }, + }, + + Results: []*atypes.Param{ + + { + Name: "message", + Types: []string{"EmailMessage"}, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &emailMessageArgs{ + hasSubject: in.Has("subject"), + hasReplyTo: in.Has("replyTo"), + hasFrom: in.Has("from"), + hasTo: in.Has("to"), + hasCc: in.Has("cc"), + hasHtml: in.Has("html"), + hasPlain: in.Has("plain"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting ReplyTo argument + if args.hasReplyTo { + aux := expr.Must(expr.Select(in, "replyTo")) + switch aux.Type() { + case h.reg.Type("String").Type(): + args.replyToString = aux.Get().(string) + case h.reg.Type("User").Type(): + args.replyToUser = aux.Get().(*sysTypes.User) + } + } + + // Converting From argument + if args.hasFrom { + aux := expr.Must(expr.Select(in, "from")) + switch aux.Type() { + case h.reg.Type("String").Type(): + args.fromString = aux.Get().(string) + case h.reg.Type("User").Type(): + args.fromUser = aux.Get().(*sysTypes.User) + } + } + + // Converting To argument + if args.hasTo { + aux := expr.Must(expr.Select(in, "to")) + switch aux.Type() { + case h.reg.Type("String").Type(): + args.toString = aux.Get().(string) + case h.reg.Type("KV").Type(): + args.toKV = aux.Get().(map[string]string) + case h.reg.Type("User").Type(): + args.toUser = aux.Get().(*sysTypes.User) + } + } + + // Converting Cc argument + if args.hasCc { + aux := expr.Must(expr.Select(in, "cc")) + switch aux.Type() { + case h.reg.Type("String").Type(): + args.ccString = aux.Get().(string) + case h.reg.Type("KV").Type(): + args.ccKV = aux.Get().(map[string]string) + case h.reg.Type("User").Type(): + args.ccUser = aux.Get().(*sysTypes.User) + } + } + + // Converting Html argument + if args.hasHtml { + aux := expr.Must(expr.Select(in, "html")) + switch aux.Type() { + case h.reg.Type("String").Type(): + args.htmlString = aux.Get().(string) + case h.reg.Type("Reader").Type(): + args.htmlStream = aux.Get().(io.Reader) + } + } + + // Converting Plain argument + if args.hasPlain { + aux := expr.Must(expr.Select(in, "plain")) + switch aux.Type() { + case h.reg.Type("String").Type(): + args.plainString = aux.Get().(string) + case h.reg.Type("Reader").Type(): + args.plainStream = aux.Get().(io.Reader) + } + } + + var results *emailMessageResults + if results, err = h.message(ctx, args); err != nil { + return + } + + out = &expr.Vars{} + + { + // converting results.Message (*emailMessage) to EmailMessage + var ( + tval expr.TypedValue + ) + + if tval, err = h.reg.Type("EmailMessage").Cast(results.Message); err != nil { + return + } else if err = expr.Assign(out, "message", tval); err != nil { + return + } + } + + return + }, + } +} + +type ( + emailSendMessageArgs struct { + hasMessage bool + Message *emailMessage + } +) + +// SendMessage function Sends email message +// +// expects implementation of sendMessage function: +// func (h emailHandler) sendMessage(ctx context.Context, args *emailSendMessageArgs) (err error) { +// return +// } +func (h emailHandler) SendMessage() *atypes.Function { + return &atypes.Function{ + Ref: "emailSendMessage", + Kind: "function", + Labels: map[string]string(nil), + Meta: &atypes.FunctionMeta{ + Short: "Sends email message", + }, + + Parameters: []*atypes.Param{ + { + Name: "message", + Types: []string{"EmailMessage"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Message to be sent", + }, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &emailSendMessageArgs{ + hasMessage: in.Has("message"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + return out, h.sendMessage(ctx, args) + }, + } +} + +type ( + emailSetSubjectArgs struct { + hasMessage bool + Message *emailMessage + + hasSubject bool + Subject string + } +) + +// SetSubject function Sets message subject +// +// expects implementation of setSubject function: +// func (h emailHandler) setSubject(ctx context.Context, args *emailSetSubjectArgs) (err error) { +// return +// } +func (h emailHandler) SetSubject() *atypes.Function { + return &atypes.Function{ + Ref: "emailSetSubject", + Kind: "function", + Labels: map[string]string(nil), + Meta: &atypes.FunctionMeta{ + Short: "Sets message subject", + }, + + Parameters: []*atypes.Param{ + { + Name: "message", + Types: []string{"EmailMessage"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Message to be sent", + }, + }, + { + Name: "subject", + Types: []string{"String"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Subject", + }, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &emailSetSubjectArgs{ + hasMessage: in.Has("message"), + hasSubject: in.Has("subject"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + return out, h.setSubject(ctx, args) + }, + } +} + +type ( + emailSetHeadersArgs struct { + hasMessage bool + Message *emailMessage + + hasHeaders bool + Headers map[string][]string + } +) + +// SetHeaders function Sets message headers (overrides any existing headers, subject, recipients) +// +// expects implementation of setHeaders function: +// func (h emailHandler) setHeaders(ctx context.Context, args *emailSetHeadersArgs) (err error) { +// return +// } +func (h emailHandler) SetHeaders() *atypes.Function { + return &atypes.Function{ + Ref: "emailSetHeaders", + Kind: "function", + Labels: map[string]string(nil), + Meta: &atypes.FunctionMeta{ + Short: "Sets message headers (overrides any existing headers, subject, recipients)", + }, + + Parameters: []*atypes.Param{ + { + Name: "message", + Types: []string{"EmailMessage"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Message to be sent", + }, + }, + { + Name: "headers", + Types: []string{"KVV"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Headers", + }, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &emailSetHeadersArgs{ + hasMessage: in.Has("message"), + hasHeaders: in.Has("headers"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + return out, h.setHeaders(ctx, args) + }, + } +} + +type ( + emailSetHeaderArgs struct { + hasMessage bool + Message *emailMessage + + hasName bool + Name string + + hasValue bool + Value string + } +) + +// SetHeader function Appends value or removes specific header, +// +// expects implementation of setHeader function: +// func (h emailHandler) setHeader(ctx context.Context, args *emailSetHeaderArgs) (err error) { +// return +// } +func (h emailHandler) SetHeader() *atypes.Function { + return &atypes.Function{ + Ref: "emailSetHeader", + Kind: "function", + Labels: map[string]string(nil), + Meta: &atypes.FunctionMeta{ + Short: "Appends value or removes specific header,", + }, + + Parameters: []*atypes.Param{ + { + Name: "message", + Types: []string{"EmailMessage"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Message to be sent", + }, + }, + { + Name: "name", + Types: []string{"String"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Value", + }, + }, + { + Name: "value", + Types: []string{"String"}, + Meta: &atypes.ParamMeta{ + Label: "Value", + Description: "Raw header value. Omiting value will remove header.", + }, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &emailSetHeaderArgs{ + hasMessage: in.Has("message"), + hasName: in.Has("name"), + hasValue: in.Has("value"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + return out, h.setHeader(ctx, args) + }, + } +} + +type ( + emailSetAddressArgs struct { + hasMessage bool + Message *emailMessage + + hasType bool + Type string + + hasAddress bool + Address string + + hasName bool + Name string + } +) + +// SetAddress function Adds new recipient, sender or reply-to address +// +// expects implementation of setAddress function: +// func (h emailHandler) setAddress(ctx context.Context, args *emailSetAddressArgs) (err error) { +// return +// } +func (h emailHandler) SetAddress() *atypes.Function { + return &atypes.Function{ + Ref: "emailSetAddress", + Kind: "function", + Labels: map[string]string(nil), + Meta: &atypes.FunctionMeta{ + Short: "Adds new recipient, sender or reply-to address", + }, + + Parameters: []*atypes.Param{ + { + Name: "message", + Types: []string{"EmailMessage"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Message to be sent", + }, + }, + { + Name: "type", + Types: []string{"String"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Type", + Description: "One of From", + }, + }, + { + Name: "address", + Types: []string{"String"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Address", + }, + }, + { + Name: "name", + Types: []string{"String"}, + Meta: &atypes.ParamMeta{ + Label: "Name", + }, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &emailSetAddressArgs{ + hasMessage: in.Has("message"), + hasType: in.Has("type"), + hasAddress: in.Has("address"), + hasName: in.Has("name"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + return out, h.setAddress(ctx, args) + }, + } +} + +type ( + emailAttachArgs struct { + hasMessage bool + Message *emailMessage + + hasContent bool + Content interface{} + contentStream io.Reader + contentString string + + hasName bool + Name string + } +) + +func (a emailAttachArgs) GetContent() (bool, io.Reader, string) { + return a.hasContent, a.contentStream, a.contentString +} + +// Attach function Attach content to an email message +// +// expects implementation of attach function: +// func (h emailHandler) attach(ctx context.Context, args *emailAttachArgs) (err error) { +// return +// } +func (h emailHandler) Attach() *atypes.Function { + return &atypes.Function{ + Ref: "emailAttach", + Kind: "function", + Labels: map[string]string(nil), + Meta: &atypes.FunctionMeta{ + Short: "Attach content to an email message", + }, + + Parameters: []*atypes.Param{ + { + Name: "message", + Types: []string{"EmailMessage"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Message to be sent", + }, + }, + { + Name: "content", + Types: []string{"Reader", "String"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Content", + }, + }, + { + Name: "name", + Types: []string{"String"}, + Meta: &atypes.ParamMeta{ + Label: "Name", + }, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &emailAttachArgs{ + hasMessage: in.Has("message"), + hasContent: in.Has("content"), + hasName: in.Has("name"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + // Converting Content argument + if args.hasContent { + aux := expr.Must(expr.Select(in, "content")) + switch aux.Type() { + case h.reg.Type("Reader").Type(): + args.contentStream = aux.Get().(io.Reader) + case h.reg.Type("String").Type(): + args.contentString = aux.Get().(string) + } + } + + return out, h.attach(ctx, args) + }, + } +} + +type ( + emailEmbedArgs struct { + hasMessage bool + Message *emailMessage + + hasContent bool + Content io.Reader + + hasName bool + Name string + } +) + +// Embed function Embed file to an email message +// +// expects implementation of embed function: +// func (h emailHandler) embed(ctx context.Context, args *emailEmbedArgs) (err error) { +// return +// } +func (h emailHandler) Embed() *atypes.Function { + return &atypes.Function{ + Ref: "emailEmbed", + Kind: "function", + Labels: map[string]string(nil), + Meta: &atypes.FunctionMeta{ + Short: "Embed file to an email message", + }, + + Parameters: []*atypes.Param{ + { + Name: "message", + Types: []string{"EmailMessage"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Message to be sent", + }, + }, + { + Name: "content", + Types: []string{"Reader"}, Required: true, + Meta: &atypes.ParamMeta{ + Label: "Content", + }, + }, + { + Name: "name", + Types: []string{"String"}, + Meta: &atypes.ParamMeta{ + Label: "Name", + }, + }, + }, + + Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) { + var ( + args = &emailEmbedArgs{ + hasMessage: in.Has("message"), + hasContent: in.Has("content"), + hasName: in.Has("name"), + } + ) + + if err = in.Decode(args); err != nil { + return + } + + return out, h.embed(ctx, args) + }, + } +} diff --git a/automation/automation/email_handler.go b/automation/automation/email_handler.go new file mode 100644 index 000000000..30a7fcfd7 --- /dev/null +++ b/automation/automation/email_handler.go @@ -0,0 +1,224 @@ +package automation + +import ( + "context" + "fmt" + "github.com/cortezaproject/corteza-server/pkg/mail" + sysTypes "github.com/cortezaproject/corteza-server/system/types" + gomail "gopkg.in/mail.v2" + "io" + "io/ioutil" + "strings" +) + +type ( + emailHandler struct { + reg emailHandlerRegistry + } + + messageArgs interface { + GetReplyTo() (bool, string, *sysTypes.User) + GetFrom() (bool, string, *sysTypes.User) + GetTo() (bool, string, map[string]string, *sysTypes.User) + GetCc() (bool, string, map[string]string, *sysTypes.User) + GetHtml() (bool, string, io.Reader) + GetPlain() (bool, string, io.Reader) + } +) + +func EmailHandler(reg emailHandlerRegistry) *emailHandler { + h := &emailHandler{ + reg: reg, + } + + h.register() + return h +} + +func (h emailHandler) send(_ context.Context, args *emailSendArgs) (err error) { + msg := mail.New() + if err = h.procArgs(msg, args.Subject, args); err != nil { + return + } + + return mail.Send(msg) +} + +func (h emailHandler) message(_ context.Context, args *emailMessageArgs) (r *emailMessageResults, err error) { + msg := mail.New() + if err = h.procArgs(msg, args.Subject, args); err != nil { + return + } + + return &emailMessageResults{Message: &emailMessage{msg: msg}}, nil +} + +func (h emailHandler) sendMessage(_ context.Context, args *emailSendMessageArgs) (err error) { + if args.Message.msg == nil { + return fmt.Errorf("email message not initialized") + } + + return mail.Send(args.Message.msg) +} + +func (h emailHandler) setSubject(_ context.Context, args *emailSetSubjectArgs) (err error) { + if args.Message.msg == nil { + return fmt.Errorf("email message not initialized") + } + + args.Message.msg.SetHeader("Subject", args.Subject) + return nil +} + +func (h emailHandler) setHeaders(_ context.Context, args *emailSetHeadersArgs) (err error) { + if args.Message.msg == nil { + return fmt.Errorf("email message not initialized") + } + + args.Message.msg.SetHeaders(args.Headers) + return nil +} + +func (h emailHandler) setHeader(_ context.Context, args *emailSetHeaderArgs) (err error) { + if args.Message.msg == nil { + return fmt.Errorf("email message not initialized") + } + + if args.hasValue { + args.Message.msg.SetHeader(args.Name, append(args.Message.msg.GetHeader(args.Name), args.Value)...) + } else { + args.Message.msg.SetHeader(args.Name) + } + return nil +} + +func (h emailHandler) setAddress(_ context.Context, args *emailSetAddressArgs) (err error) { + if args.Message.msg == nil { + return fmt.Errorf("email message not initialized") + } + + args.Message.msg.SetAddressHeader(args.Type, args.Address, args.Name) + return nil +} + +func (h emailHandler) attach(_ context.Context, args *emailAttachArgs) (err error) { + if args.Message.msg == nil { + return fmt.Errorf("email message not initialized") + } + + var ( + r = args.contentStream + ) + + if r == nil { + r = strings.NewReader(args.contentString) + } + + args.Message.msg.AttachReader(args.Name, r) + return +} + +func (h emailHandler) embed(_ context.Context, args *emailEmbedArgs) (err error) { + if args.Message.msg == nil { + return fmt.Errorf("email message not initialized") + } + + args.Message.msg.EmbedReader(args.Name, args.Content) + return +} + +func (h emailHandler) procArgs(msg *gomail.Message, subject string, args messageArgs) (err error) { + msg.SetHeader("Subject", subject) + + var ( + // if text/plain exists, + // we'll add text/html as alternative body! + hasPlain bool + ) + + if has, s, r := args.GetPlain(); has { + if r != nil { + aux, _ := ioutil.ReadAll(r) + s = string(aux) + } + + msg.SetBody("text/plain", s) + hasPlain = true + } + + if has, s, r := args.GetHtml(); has { + if r != nil { + aux, _ := ioutil.ReadAll(r) + s = string(aux) + } + + if hasPlain { + msg.AddAlternative("text/html", s) + } else { + msg.SetBody("text/html", s) + } + } + + if has, s, m, u := args.GetTo(); has { + if err = h.procEmailRecipients(msg, "To", s, m, u); err != nil { + return + } + } + + if has, s, m, u := args.GetCc(); has { + if err = h.procEmailRecipients(msg, "Cc", s, m, u); err != nil { + return + } + } + + if has, s, u := args.GetReplyTo(); has { + if err = h.procEmailRecipients(msg, "ReplyTo", s, nil, u); err != nil { + return + } + } + + if has, s, u := args.GetFrom(); has { + if err = h.procEmailRecipients(msg, "From", s, nil, u); err != nil { + return + } + } + + return +} + +func (h emailHandler) procEmailRecipients(msg *gomail.Message, field string, s string, mm map[string]string, u *sysTypes.User) (err error) { + var ( + rr = make([]string, 0) + ) + + switch { + case len(s) > 0: + var ( + email string + name string + ) + + name = "" + if spaceAt := strings.Index(s, " "); spaceAt > -1 { + // proc ("foo@bar.baz foo baz") + email, name = s[:spaceAt], strings.TrimSpace(s[spaceAt+1:]) + } else { + // proc + email = s + } + + rr = append(rr, msg.FormatAddress(email, name)) + + case len(mm) > 0: + for email, name := range mm { + rr = append(rr, msg.FormatAddress(email, name)) + } + + case u != nil: + rr = append(rr, msg.FormatAddress(u.Email, u.Name)) + + } + + msg.SetHeader(field, rr...) + return nil +} diff --git a/automation/automation/email_handler.yaml b/automation/automation/email_handler.yaml new file mode 100644 index 000000000..5b5f39f37 --- /dev/null +++ b/automation/automation/email_handler.yaml @@ -0,0 +1,171 @@ +name: email + +imports: + - io + - sysTypes github.com/cortezaproject/corteza-server/system/types + + +messageParams: &messageParams + subject: + meta: + label: Subject + types: + - { wf: String } + + replyTo: + meta: + label: Reply to + types: + - { wf: String } + - { wf: User, go: '*sysTypes.User' } + from: + meta: + label: Sender + types: + - { wf: String } + - { wf: User, go: '*sysTypes.User' } + to: + meta: + label: Recipients + types: + - { wf: String } + - { wf: KV } + - { wf: User, go: '*sysTypes.User' } + cc: + meta: + label: CC + types: + - { wf: String } + - { wf: KV } + - { wf: User, go: '*sysTypes.User' } + + html: + meta: + label: HTML message body + types: + - { wf: String, suffix: String } + - { wf: Reader, suffix: Stream } + + plain: + meta: + label: Plain text message body + types: + - { wf: String, suffix: String } + - { wf: Reader, suffix: Stream } + +messageParam: &messageParam + required: true + meta: + label: Message to be sent + types: + - { wf: EmailMessage } + +messageResults: &messageResults + message: { wf: EmailMessage } + +functions: + send: + kind: function + meta: + short: Sends email message with basic parameters + params: *messageParams + + message: + kind: function + meta: + short: Constructs new email message + params: *messageParams + results: *messageResults + + sendMessage: + kind: function + meta: + short: Sends email message + params: + message: *messageParam + + setSubject: + kind: function + meta: + short: Sets message subject + params: + message: *messageParam + subject: + required: true + meta: + label: Subject + types: + - { wf: String } + + setHeaders: + kind: function + meta: + short: Sets message headers (overrides any existing headers, subject, recipients) + params: + message: *messageParam + headers: + required: true + meta: + label: Headers + types: + - { wf: KVV } + + setHeader: + kind: function + meta: + short: Appends value or removes specific header, + params: + message: *messageParam + name: + required: true + meta: { label: Value } + types: [ { wf: String } ] + value: + meta: { label: Value, description: Raw header value. Omiting value will remove header. } + types: [ { wf: String } ] + + setAddress: + kind: function + meta: + short: Adds new recipient, sender or reply-to address + params: + message: *messageParam + type: + required: true + meta: { label: Type, description: One of From, ReplyTo, To, CC, BCC } + types: [ { wf: String } ] + address: + required: true + meta: { label: Address } + types: [ { wf: String } ] + name: + meta: { label: Name } + types: [ { wf: String } ] + + attach: + kind: function + meta: + short: Attach content to an email message + params: + message: *messageParam + content: + required: true + meta: { label: Content } + types: [ { wf: Reader, suffix: Stream }, { wf: String } ] + name: + meta: { label: Name } + types: [ { wf: String } ] + + embed: + kind: function + meta: + short: Embed file to an email message + params: + message: *messageParam + content: + required: true + meta: { label: Content } + types: [ { wf: Reader } ] + name: + meta: { label: Name } + types: [ { wf: String } ] diff --git a/automation/automation/expr_types.gen.go b/automation/automation/expr_types.gen.go new file mode 100644 index 000000000..67b7c1e4d --- /dev/null +++ b/automation/automation/expr_types.gen.go @@ -0,0 +1,56 @@ +package automation + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// automation/automation/expr_types.yaml + +import ( + "context" + "fmt" + . "github.com/cortezaproject/corteza-server/pkg/expr" +) + +var _ = context.Background +var _ = fmt.Errorf + +// EmailMessage is an expression type, wrapper for *emailMessage type +type EmailMessage struct{ value *emailMessage } + +// NewEmailMessage creates new instance of EmailMessage expression type +func NewEmailMessage(val interface{}) (*EmailMessage, error) { + if c, err := CastToEmailMessage(val); err != nil { + return nil, fmt.Errorf("unable to create EmailMessage: %w", err) + } else { + return &EmailMessage{value: c}, nil + } +} + +// Return underlying value on EmailMessage +func (t EmailMessage) Get() interface{} { return t.value } + +// Return underlying value on EmailMessage +func (t EmailMessage) GetValue() *emailMessage { return t.value } + +// Return type name +func (EmailMessage) Type() string { return "EmailMessage" } + +// Convert value to *emailMessage +func (EmailMessage) Cast(val interface{}) (TypedValue, error) { + return NewEmailMessage(val) +} + +// Assign new value to EmailMessage +// +// value is first passed through CastToEmailMessage +func (t *EmailMessage) Assign(val interface{}) error { + if c, err := CastToEmailMessage(val); err != nil { + return err + } else { + t.value = c + return nil + } +} diff --git a/automation/automation/expr_types.go b/automation/automation/expr_types.go new file mode 100644 index 000000000..e698e2da5 --- /dev/null +++ b/automation/automation/expr_types.go @@ -0,0 +1,29 @@ +package automation + +import ( + "fmt" + "github.com/cortezaproject/corteza-server/pkg/expr" + "gopkg.in/mail.v2" +) + +type ( + emailMessage struct { + // only basic implementation for now + // we can manipulate mail.Message internals through + // specialized wf functions (message, setSubject, setHeaders, ...) + msg *mail.Message + } +) + +func CastToEmailMessage(val interface{}) (out *emailMessage, err error) { + switch val := expr.UntypedValue(val).(type) { + case *emailMessage: + if val.msg == nil { + val.msg = mail.NewMessage() + } + + return val, nil + default: + return nil, fmt.Errorf("unable to cast type %T to %T", val, out) + } +} diff --git a/automation/automation/expr_types.yaml b/automation/automation/expr_types.yaml new file mode 100644 index 000000000..2df9cc83f --- /dev/null +++ b/automation/automation/expr_types.yaml @@ -0,0 +1,18 @@ +package: automation + +types: + EmailMessage: + # using ad-hoc type for now, we'll port this to something internal + # when email-gateway is implemented + as: '*emailMessage' + struct: +# - { name: 'subject', exprType: 'String', goType: 'string' } +# - { name: 'addresses', exprType: 'Array', goType: '[]*emailAddress' } +# - { name: 'headers', exprType: 'KVV', goType: 'map[string][]string' } +# - { name: 'replyTo', exprType: 'KV', goType: 'map[string]string', mode: ro } +# - { name: 'to', exprType: 'KV', goType: 'map[string]string', mode: ro } +# - { name: 'cc', exprType: 'KV', goType: 'map[string]string', mode: ro } +# - { name: 'bcc', exprType: 'KV', goType: 'map[string]string', mode: ro } +# - { name: 'parts', exprType: 'Any', goType: 'map[string]io.Reader', mode: ro } +# - { name: 'embedded', exprType: 'Any', goType: 'map[string]io.Reader', mode: ro } +# - { name: 'attachments', exprType: 'Any', goType: 'map[string]io.Reader', mode: ro } diff --git a/automation/service/service.go b/automation/service/service.go index 9f259601c..c1f77213d 100644 --- a/automation/service/service.go +++ b/automation/service/service.go @@ -111,12 +111,15 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, c Config) &expr.KV{}, &expr.KVV{}, &expr.Reader{}, + + &automation.EmailMessage{}, ) automation.HttpRequestHandler(Registry()) automation.LogHandler(Registry()) automation.LoopHandler(Registry(), DefaultWorkflow.parser) automation.CorredorHandler(Registry(), corredor.Service()) + automation.EmailHandler(Registry()) return } diff --git a/pkg/codegen/afunc.go b/pkg/codegen/afunc.go index 122e8a0b6..f9f336bc3 100644 --- a/pkg/codegen/afunc.go +++ b/pkg/codegen/afunc.go @@ -168,6 +168,9 @@ func expandAutomationFunctionTypes(ff []*aFuncDefs, tt []*exprTypesDef) { if t.GoType == "" { t.GoType = ti[t.WorkflowType].As + if "[]TypedValue" == t.GoType { + t.GoType = "[]expr.TypedValue" + } } if t.Suffix == "" && len(p.Types) > 1 { @@ -183,6 +186,9 @@ func expandAutomationFunctionTypes(ff []*aFuncDefs, tt []*exprTypesDef) { if r.GoType == "" { r.GoType = ti[r.WorkflowType].As + if "[]TypedValue" == r.GoType { + r.GoType = "[]expr.TypedValue" + } } } } diff --git a/pkg/codegen/assets/afunc.gen.go.tpl b/pkg/codegen/assets/afunc.gen.go.tpl index 43ac76f9e..c765683b6 100644 --- a/pkg/codegen/assets/afunc.gen.go.tpl +++ b/pkg/codegen/assets/afunc.gen.go.tpl @@ -75,7 +75,8 @@ type ( {{ range .Params }} {{- if gt (len .Types) 1 }} {{ $name := .Name }} -func (a {{ $ARGS }}) {{ export "get" $name }}() (bool, {{ range .Types }}{{ .GoType }},{{ end }}) { +{{ $isArray := .IsArray }} +func (a {{ $ARGS }}) {{ export "get" $name }}() (bool, {{ range .Types }}{{ if $isArray }}[]{{ end }}{{ .GoType }},{{ end }}) { return a.has{{ export $name }}{{ range .Types }}, a.{{ $name }}{{ export .Suffix }}{{ end }} } {{- end }} @@ -250,6 +251,7 @@ func (h {{ $.Name }}Handler) {{ export .Name }}() *atypes.Function { {{ range . }} {{ $name := .Name }} + {{ $isArray := .IsArray }} {{ if gt (len .Types) 1 }} // Converting {{ export .Name }} argument if args.has{{ export .Name }} { @@ -257,7 +259,7 @@ func (h {{ $.Name }}Handler) {{ export .Name }}() *atypes.Function { switch aux.Type() { {{- range .Types }} case h.reg.Type({{ printf "%q" .WorkflowType }}).Type(): - args.{{ $name }}{{ export .Suffix }} = aux.Get().({{ .GoType }}) + args.{{ $name }}{{ export .Suffix }} = aux.Get().({{ if $isArray }}[]{{ end }}{{ .GoType }}) {{- end -}} } } diff --git a/pkg/codegen/assets/expr_types.gen.go.tpl b/pkg/codegen/assets/expr_types.gen.go.tpl index 737cd0ca7..26f0dc567 100644 --- a/pkg/codegen/assets/expr_types.gen.go.tpl +++ b/pkg/codegen/assets/expr_types.gen.go.tpl @@ -8,7 +8,6 @@ package {{ .Package }} // Definitions file that controls how this file is generated: // {{ .Source }} -{{ if .Imports }} import ( "context" "fmt" @@ -19,13 +18,10 @@ import ( . "github.com/cortezaproject/corteza-server/pkg/expr" {{- end }} ) -{{ end }} - var _ = context.Background var _ = fmt.Errorf - {{ range $exprType, $def := .Types }} // {{ $exprType }} is an expression type, wrapper for {{ $def.As }} type type {{ $exprType }} struct{ value {{ $def.As }} }