Skip to content

Commit

Permalink
feat: add bridges to config
Browse files Browse the repository at this point in the history
  • Loading branch information
ItsNotGoodName committed Jul 23, 2022
1 parent 2d9d353 commit 9a812c1
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 65 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ endpoints: # Endpoints for envelopes
template: |
{{ .Message.Subject }}
{{ .Message.Text }}
bridges:
- from: [email protected]
endpoints:
- telegram endpoint
- console endpoint
```
## Usage
Expand Down
10 changes: 10 additions & 0 deletions config/bridges.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package config

type Bridge struct {
Name string `json:"name" mapstructure:"name"`
From string `json:"from" mapstructure:"from"`
To string `json:"to" mapstructure:"to"`
FromRegex string `json:"from_regex" mapstructure:"from_regex"`
ToRegex string `json:"to_regex" mapstructure:"to_regex"`
Endpoints []string `json:"endpoints" mapstructure:"endpoints"`
}
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Config struct {
HTTP HTTP `json:"http" mapstructure:"http"`
SMTP SMTP `json:"smtp" mapstructure:"smtp"`
Endpoints []Endpoint `json:"endpoints" mapstructure:"endpoints"`
Bridges []Bridge `json:"bridges" mapstructure:"bridges"`
}

func New() *Config {
Expand Down
82 changes: 20 additions & 62 deletions core/bridge/bridge.go
Original file line number Diff line number Diff line change
@@ -1,71 +1,29 @@
package bridge

import (
"context"
"log"

"github.com/ItsNotGoodName/smtpbridge/core"
"github.com/ItsNotGoodName/smtpbridge/core/endpoint"
"github.com/ItsNotGoodName/smtpbridge/core/envelope"
"github.com/ItsNotGoodName/smtpbridge/core/event"
)

type (
Service interface{}
)

type BridgeService struct {
eventPub *event.Pub
envelopeService envelope.Service
endpointService endpoint.Service
}

func NewBridgeService(pub *event.Pub, envelopeService envelope.Service, endpointService endpoint.Service) *BridgeService {
return &BridgeService{
eventPub: pub,
envelopeService: envelopeService,
endpointService: endpointService,
Bridge struct {
Filter Filter
Endpoints []string
}
}

func (bs *BridgeService) Run(ctx context.Context, doneC chan<- struct{}) {
log.Println("bridge.BridgeService.Run: started")

eventChan := make(chan event.Event, 100)

bs.eventPub.Subscribe(event.TopicEnvelopeCreated, eventChan)
for {
select {
case <-ctx.Done():
log.Println("bridge.BridgeService.Run: stopped")
doneC <- struct{}{}
return
case event := <-eventChan:
env := event.Data.(*envelope.Envelope)

// Convert envelope attachments to endpoint attachments
atts := []endpoint.Attachment{}
for _, att := range env.Attachments {
data, err := bs.envelopeService.GetData(ctx, &att)
if err != nil && err != core.ErrDataNotFound {
log.Println("bridge.BridgeService.Run:", err)
continue
}

atts = append(atts, endpoint.NewAttachment(&att, data))
}
CreateBridgeRequest struct {
Name string
From string
FromRegex string
To string
ToRegex string
Endpoints []string
}

ends := bs.endpointService.ListEndpoint()
Service interface {
CreateBridge(req *CreateBridgeRequest) error
ListBridge() []Bridge
}
)

// Send to all endpoints
for _, end := range ends {
text, err := end.Text(env)
if err != nil {
log.Println("bridge.BridgeService.Run:", err)
} else {
end.Sender.Send(ctx, text, atts)
}
}
}
func NewBridge(filter Filter, endpoints []string) Bridge {
return Bridge{
Filter: filter,
Endpoints: endpoints,
}
}
70 changes: 70 additions & 0 deletions core/bridge/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package bridge

import (
"regexp"

"github.com/ItsNotGoodName/smtpbridge/core/envelope"
)

type Filter struct {
To string
From string
toRegexp *regexp.Regexp
fromRegexp *regexp.Regexp
}

func NewFilter(from, to, fromRegex, toRegex string) (Filter, error) {
var fromRegexp, toRegexp *regexp.Regexp
var err error
if fromRegex != "" {
fromRegexp, err = regexp.Compile(fromRegex)
if err != nil {
return Filter{}, err
}
}
if toRegex != "" {
toRegexp, err = regexp.Compile(toRegex)
if err != nil {
return Filter{}, err
}
}

return Filter{
From: from,
To: to,
fromRegexp: fromRegexp,
toRegexp: toRegexp,
}, nil
}

func (f *Filter) Match(msg *envelope.Message) bool {
if f.toRegexp != nil {
found := false
for to := range msg.To {
if f.toRegexp.MatchString(to) {
found = true
break
}
}

if !found {
return false
}
} else if f.To != "" {
if _, ok := msg.To[f.To]; !ok {
return false
}
}

if f.fromRegexp != nil {
if !f.fromRegexp.MatchString(msg.From) {
return false
}
} else if f.From != "" {
if msg.From != f.From {
return false
}
}

return true
}
44 changes: 44 additions & 0 deletions core/bridge/filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package bridge

import (
"testing"

"github.com/ItsNotGoodName/smtpbridge/core/envelope"
"github.com/stretchr/testify/assert"
)

func TestFilter(t *testing.T) {
type FilterTest struct {
Filter Filter
Match bool
}

newFilter := func(to, from, toRegex, fromRegex string) Filter {
filter, err := NewFilter(from, to, fromRegex, toRegex)
assert.Nil(t, err)
return filter
}

msg := envelope.Message{
From: "foo",
To: map[string]struct{}{"bar": {}},
}

tests := []FilterTest{
{Filter: newFilter("bar", "", "", ""), Match: true},
{Filter: newFilter("bar", "foo", "", ""), Match: true},
{Filter: newFilter("bar", "foorr", "", ""), Match: false},
{Filter: newFilter("barr", "", "", ""), Match: false},
{Filter: newFilter("", "barrr", "", ""), Match: false},
{Filter: newFilter("", "", "", "f.$"), Match: false},
{Filter: newFilter("", "", "", "f"), Match: true},
{Filter: newFilter("", "", "b", ""), Match: true},
{Filter: newFilter("bar", "", "f", ""), Match: false},
{Filter: newFilter("bar", "", "b", ""), Match: true},
{Filter: newFilter("bar", "", "b", "x"), Match: false},
}

for _, test := range tests {
assert.Equal(t, test.Filter.Match(&msg), test.Match)
}
}
137 changes: 137 additions & 0 deletions core/bridge/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package bridge

import (
"context"
"errors"
"fmt"
"log"
"sync"

"github.com/ItsNotGoodName/smtpbridge/core"
"github.com/ItsNotGoodName/smtpbridge/core/endpoint"
"github.com/ItsNotGoodName/smtpbridge/core/envelope"
"github.com/ItsNotGoodName/smtpbridge/core/event"
)

type BridgeService struct {
eventPub *event.Pub
envelopeService envelope.Service
endpointService endpoint.Service
bridgesMu sync.Mutex
bridges []Bridge
}

func NewBridgeService(pub *event.Pub, envelopeService envelope.Service, endpointService endpoint.Service) *BridgeService {
return &BridgeService{
eventPub: pub,
envelopeService: envelopeService,
endpointService: endpointService,
}
}

func (bs *BridgeService) ListBridge() []Bridge {
bridges := []Bridge{}

bs.bridgesMu.Lock()
bridges = append(bridges, bs.bridges...)
bs.bridgesMu.Unlock()

return bridges
}

func (bs *BridgeService) CreateBridge(req *CreateBridgeRequest) error {
for _, end := range req.Endpoints {
if _, err := bs.endpointService.GetEndpoint(end); err != nil {
return fmt.Errorf("%w: '%s'", err, end)
}
}

filter, err := NewFilter(req.To, req.From, req.ToRegex, req.FromRegex)
if err != nil {
return err
}

bridge := NewBridge(filter, req.Endpoints)
bs.bridgesMu.Lock()
bs.bridges = append(bs.bridges, bridge)
bs.bridgesMu.Unlock()

return nil
}

func (bs *BridgeService) send(ctx context.Context, env *envelope.Envelope) error {
// Convert envelope attachments to endpoint attachments
atts := []endpoint.Attachment{}
for _, att := range env.Attachments {
data, err := bs.envelopeService.GetData(ctx, &att)
if err != nil {
if errors.Is(err, core.ErrDataNotFound) {
log.Println("bridge.BridgeService.send:", err)
continue
}
return err
}

atts = append(atts, endpoint.NewAttachment(&att, data))
}

for _, brid := range bs.ListBridge() {
// Match bridge
if !brid.Filter.Match(&env.Message) {
continue
}

// Send to all endpoints
if len(brid.Endpoints) == 0 {
for _, end := range bs.endpointService.ListEndpoint() {
text, err := end.Text(env)
if err != nil {
log.Println("bridge.BridgeService.send:", err)
continue
}

end.Sender.Send(ctx, text, atts)
}

return nil
}

// Send to endpoints
for _, endpoitName := range brid.Endpoints {
end, err := bs.endpointService.GetEndpoint(endpoitName)
if err != nil {
log.Println("bridge.BridgeService.Run:", err)
continue
}

text, err := end.Text(env)
if err != nil {
log.Println("bridge.BridgeService.Run:", err)
continue
}

end.Sender.Send(ctx, text, atts)
}
}

return nil
}

func (bs *BridgeService) Run(ctx context.Context, doneC chan<- struct{}) {
log.Println("bridge.BridgeService.Run: started")

eventChan := make(chan event.Event, 100)
bs.eventPub.Subscribe(event.TopicEnvelopeCreated, eventChan)
for {
select {
case <-ctx.Done():
log.Println("bridge.BridgeService.Run: stopped")
doneC <- struct{}{}
return
case event := <-eventChan:
if err := bs.send(ctx, event.Data.(*envelope.Envelope)); err != nil {
log.Println("bridge.BridgeService.Run:", err)
}
}
}
}
5 changes: 4 additions & 1 deletion left/http/controller/envelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ func EnvelopeSendPost(envelopeService envelope.Service, endpointService endpoint
if !noAttachments {
for _, att := range env.Attachments {
data, err := envelopeService.GetData(ctx, &att)
if err != nil && err != core.ErrDataNotFound {
if err != nil {
if errors.Is(err, core.ErrDataNotFound) {
continue
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
Expand Down
Loading

0 comments on commit 9a812c1

Please sign in to comment.