rename comm interfaces
This commit is contained in:
parent
0fb7171198
commit
4f9734d615
9 changed files with 200 additions and 105 deletions
|
@ -3,6 +3,7 @@ package comm
|
|||
import (
|
||||
"github.com/eternal-flame-AD/yoake/internal/auth"
|
||||
"github.com/eternal-flame-AD/yoake/internal/comm/model"
|
||||
"github.com/eternal-flame-AD/yoake/internal/util"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
|
@ -13,7 +14,7 @@ type CommStatusResponse struct {
|
|||
} `json:"communicators"`
|
||||
}
|
||||
|
||||
func (c *CommProvider) RegisterAPIRoute(g *echo.Group) {
|
||||
func (c *Communicator) RegisterAPIRoute(g *echo.Group) {
|
||||
send := g.Group("/send", auth.RequireMiddleware(auth.RoleAdmin))
|
||||
{
|
||||
send.POST("", func(ctx echo.Context) error {
|
||||
|
@ -49,11 +50,11 @@ func (c *CommProvider) RegisterAPIRoute(g *echo.Group) {
|
|||
SupportedMIME []string
|
||||
}{
|
||||
Method: comm,
|
||||
SupportedMIME: c.communicators[comm].SupportedMIME(),
|
||||
SupportedMIME: c.commMethods[comm].SupportedMIME(),
|
||||
})
|
||||
}
|
||||
for key, comm := range c.communicators {
|
||||
if !contains(c.fallbackCommunicators, key) {
|
||||
for key, comm := range c.commMethods {
|
||||
if !util.Contain(c.fallbackCommunicators, key) {
|
||||
communicators = append(communicators, struct {
|
||||
Method string
|
||||
SupportedMIME []string
|
||||
|
|
108
internal/comm/communicator.go
Normal file
108
internal/comm/communicator.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package comm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/eternal-flame-AD/yoake/internal/comm/email"
|
||||
"github.com/eternal-flame-AD/yoake/internal/comm/gotify"
|
||||
"github.com/eternal-flame-AD/yoake/internal/comm/model"
|
||||
"github.com/eternal-flame-AD/yoake/internal/util"
|
||||
)
|
||||
|
||||
type Communicator struct {
|
||||
commMethods map[string]model.CommMethod
|
||||
fallbackCommunicators []string
|
||||
}
|
||||
|
||||
var (
|
||||
errMethodNotSupported = errors.New("method not supported")
|
||||
)
|
||||
|
||||
func (c *Communicator) actualSendGenericMessage(tryMethod string, message model.GenericMessage) error {
|
||||
if comm, ok := c.commMethods[tryMethod]; ok {
|
||||
if convertedMsg, err := ConvertGenericMessage(&message, comm.SupportedMIME()); err == nil {
|
||||
return comm.SendGenericMessage(*convertedMsg)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return errMethodNotSupported
|
||||
}
|
||||
|
||||
// GetMethod returns the method with the given name.
|
||||
func (c *Communicator) GetMethod(method string) model.CommMethod {
|
||||
return c.commMethods[method]
|
||||
}
|
||||
|
||||
// GetMethodsByMIME returns a list of methods that support the given MIME type as the message type, MIME convertions were considered.
|
||||
func (c *Communicator) GetMethodsByMIME(mime string) []model.CommMethod {
|
||||
var result []model.CommMethod
|
||||
for _, comm := range c.commMethods {
|
||||
if util.Contain(ConvertOutMIMEToSupportedInMIME(comm.SupportedMIME()), mime) {
|
||||
result = append(result, comm)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type ErrorSentWithFallback struct {
|
||||
OriginalError error
|
||||
OrignalMethod string
|
||||
FallbackMethod string
|
||||
}
|
||||
|
||||
func (e ErrorSentWithFallback) Error() string {
|
||||
return fmt.Sprintf("used fallback method %s because original method %s reeported error: %v", e.FallbackMethod, e.OrignalMethod, e.OriginalError)
|
||||
}
|
||||
|
||||
// SendGenericMethods sends a message using the preferred method
|
||||
// if the preferred method failed to send the message, fallback methods will be tried,
|
||||
// and an ErrorSentWithFabback will be returned if any fallback method succeeded.
|
||||
// if fallback methods failed as well the original error will be returned.
|
||||
func (c *Communicator) SendGenericMessage(preferredMethod string, message model.GenericMessage) error {
|
||||
if preferredMethod == "" {
|
||||
preferredMethod = c.fallbackCommunicators[0]
|
||||
}
|
||||
if origErr := c.actualSendGenericMessage(preferredMethod, message); origErr != nil {
|
||||
log.Printf("Failed to send message using preferred method %s: %v. trying fallback methods", preferredMethod, origErr)
|
||||
for _, fallback := range c.fallbackCommunicators {
|
||||
if fallback == preferredMethod {
|
||||
continue
|
||||
}
|
||||
if err := c.actualSendGenericMessage(fallback, message); err == nil {
|
||||
log.Printf("Sent message using fallback method %s", fallback)
|
||||
return ErrorSentWithFallback{
|
||||
OriginalError: origErr,
|
||||
OrignalMethod: preferredMethod,
|
||||
FallbackMethod: fallback,
|
||||
}
|
||||
} else {
|
||||
log.Printf("Failed to send message using fallback method %s: %v", fallback, err)
|
||||
}
|
||||
}
|
||||
return origErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitCommunicator() *Communicator {
|
||||
comm := &Communicator{
|
||||
commMethods: make(map[string]model.CommMethod),
|
||||
}
|
||||
if emailHandler, err := email.NewHandler(); err == nil {
|
||||
comm.commMethods["email"] = emailHandler
|
||||
comm.fallbackCommunicators = append(comm.fallbackCommunicators, "email")
|
||||
} else {
|
||||
log.Printf("Failed to initialize email communicator: %v", err)
|
||||
}
|
||||
if gotifyHandler, err := gotify.NewClient(); err == nil {
|
||||
comm.commMethods["gotify"] = gotifyHandler
|
||||
comm.fallbackCommunicators = append(comm.fallbackCommunicators, "gotify")
|
||||
} else {
|
||||
log.Printf("Failed to initialize gotify communicator: %v", err)
|
||||
}
|
||||
|
||||
return comm
|
||||
}
|
|
@ -12,16 +12,18 @@ import (
|
|||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/eternal-flame-AD/yoake/internal/comm/model"
|
||||
"github.com/eternal-flame-AD/yoake/internal/servetpl/funcmap"
|
||||
"github.com/eternal-flame-AD/yoake/internal/util"
|
||||
"github.com/gomarkdown/markdown"
|
||||
)
|
||||
|
||||
func contains[T comparable](s []T, e T) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
func unique[T comparable](s []T) []T {
|
||||
var result []T
|
||||
for _, e := range s {
|
||||
if !util.Contain(result, e) {
|
||||
result = append(result, e)
|
||||
}
|
||||
}
|
||||
return false
|
||||
return result
|
||||
}
|
||||
|
||||
type ErrorMIMENoOverlap struct {
|
||||
|
@ -33,8 +35,25 @@ func (e *ErrorMIMENoOverlap) Error() string {
|
|||
return fmt.Sprintf("message MIME type %s is not supported by this communicator. Supported MIME types are: %s", e.MessageMIME, e.supportedMIME)
|
||||
}
|
||||
|
||||
func ConvertOutMIMEToSupportedInMIME(outMIMEs []string) (inMIMEs []string) {
|
||||
inMIMEs = outMIMEs
|
||||
for _, out := range outMIMEs {
|
||||
if out == "text/plain" {
|
||||
inMIMEs = append(inMIMEs, "text/html", "text/markdown")
|
||||
}
|
||||
if out == "text/html" {
|
||||
inMIMEs = append(inMIMEs, "text/markdown")
|
||||
}
|
||||
}
|
||||
for _, in := range inMIMEs {
|
||||
inMIMEs = append(inMIMEs, in+"+html/template", in+"+text/template")
|
||||
}
|
||||
inMIMEs = unique(inMIMEs)
|
||||
return
|
||||
}
|
||||
|
||||
func ConvertGenericMessage(msgOrig *model.GenericMessage, supportedMIMES []string) (*model.GenericMessage, error) {
|
||||
if contains(supportedMIMES, msgOrig.MIME) {
|
||||
if util.Contain(supportedMIMES, msgOrig.MIME) {
|
||||
return msgOrig, nil
|
||||
}
|
||||
msg := *msgOrig
|
||||
|
@ -86,17 +105,17 @@ func ConvertGenericMessage(msgOrig *model.GenericMessage, supportedMIMES []strin
|
|||
}
|
||||
msg.Body = output.String()
|
||||
}
|
||||
if contains(supportedMIMES, msg.MIME) {
|
||||
if util.Contain(supportedMIMES, msg.MIME) {
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
// convert markdown to html
|
||||
if msg.MIME == "text/markdown" && !contains(supportedMIMES, "text/markdown") {
|
||||
if msg.MIME == "text/markdown" && !util.Contain(supportedMIMES, "text/markdown") {
|
||||
msg.Body = string(markdown.ToHTML([]byte(msg.Body), nil, nil))
|
||||
msg.MIME = "text/html"
|
||||
}
|
||||
// convert html to text
|
||||
if msg.MIME == "text/html" && !contains(supportedMIMES, "text/html") && contains(supportedMIMES, "text/plain") {
|
||||
if msg.MIME == "text/html" && !util.Contain(supportedMIMES, "text/html") && util.Contain(supportedMIMES, "text/plain") {
|
||||
docBuf := strings.NewReader(msg.Body)
|
||||
doc, err := goquery.NewDocumentFromReader(docBuf)
|
||||
if err != nil {
|
||||
|
@ -106,7 +125,7 @@ func ConvertGenericMessage(msgOrig *model.GenericMessage, supportedMIMES []strin
|
|||
msg.MIME = "text/plain"
|
||||
}
|
||||
|
||||
if !contains(supportedMIMES, msg.MIME) {
|
||||
if !util.Contain(supportedMIMES, msg.MIME) {
|
||||
return nil, &ErrorMIMENoOverlap{
|
||||
MessageMIME: msg.MIME,
|
||||
supportedMIME: supportedMIMES,
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
package comm
|
||||
|
||||
import (
|
||||
"github.com/eternal-flame-AD/yoake/internal/comm/model"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Communicator interface {
|
||||
SupportedMIME() []string
|
||||
SendGenericMessage(message model.GenericMessage) error
|
||||
}
|
||||
|
||||
type CommunicatorWithRoute interface {
|
||||
RegisterRoute(g *echo.Group)
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
package model
|
||||
|
||||
type GenericMessage struct {
|
||||
Subject string `json:"subject" form:"subject" query:"subject"`
|
||||
Body string `json:"body" form:"body" query:"body"`
|
||||
MIME string `json:"mime" form:"mime" query:"mime"`
|
||||
Subject string `json:"subject" form:"subject" query:"subject"`
|
||||
Body string `json:"body" form:"body" query:"body"`
|
||||
MIME string `json:"mime" form:"mime" query:"mime"`
|
||||
ThreadID uint64 `json:"thread_id" form:"thread_id" query:"thread_id"`
|
||||
|
||||
Context interface{}
|
||||
}
|
||||
|
|
20
internal/comm/model/interface.go
Normal file
20
internal/comm/model/interface.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type CommMethod interface {
|
||||
SupportedMIME() []string
|
||||
SendGenericMessage(message GenericMessage) error
|
||||
}
|
||||
|
||||
type CommMethodWithRoute interface {
|
||||
RegisterRoute(g *echo.Group)
|
||||
}
|
||||
|
||||
type Communicator interface {
|
||||
GetMethod(method string) CommMethod
|
||||
GetMethodsByMIME(mime string) []CommMethod
|
||||
SendGenericMessage(preferredMethod string, message GenericMessage) error
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
package comm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/eternal-flame-AD/yoake/internal/comm/email"
|
||||
"github.com/eternal-flame-AD/yoake/internal/comm/gotify"
|
||||
"github.com/eternal-flame-AD/yoake/internal/comm/model"
|
||||
)
|
||||
|
||||
type CommProvider struct {
|
||||
communicators map[string]Communicator
|
||||
fallbackCommunicators []string
|
||||
}
|
||||
|
||||
var (
|
||||
errMethodNotSupported = errors.New("method not supported")
|
||||
)
|
||||
|
||||
func (c *CommProvider) actualSendGenericMessage(tryMethod string, message model.GenericMessage) error {
|
||||
if comm, ok := c.communicators[tryMethod]; ok {
|
||||
if convertedMsg, err := ConvertGenericMessage(&message, comm.SupportedMIME()); err == nil {
|
||||
return comm.SendGenericMessage(*convertedMsg)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return errMethodNotSupported
|
||||
}
|
||||
|
||||
func (c *CommProvider) SendGenericMessage(preferredMethod string, message model.GenericMessage) error {
|
||||
if preferredMethod == "" {
|
||||
preferredMethod = c.fallbackCommunicators[0]
|
||||
}
|
||||
if err := c.actualSendGenericMessage(preferredMethod, message); err != nil {
|
||||
log.Printf("Failed to send message using preferred method %s: %v. trying fallback methods", preferredMethod, err)
|
||||
for _, fallback := range c.fallbackCommunicators {
|
||||
if fallback == preferredMethod {
|
||||
continue
|
||||
}
|
||||
if err := c.actualSendGenericMessage(fallback, message); err == nil {
|
||||
log.Printf("Sent message using fallback method %s", fallback)
|
||||
return nil
|
||||
} else {
|
||||
log.Printf("Failed to send message using fallback method %s: %v", fallback, err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitializeCommProvider() *CommProvider {
|
||||
comm := &CommProvider{
|
||||
communicators: make(map[string]Communicator),
|
||||
}
|
||||
if emailHandler, err := email.NewHandler(); err == nil {
|
||||
comm.communicators["email"] = emailHandler
|
||||
comm.fallbackCommunicators = append(comm.fallbackCommunicators, "email")
|
||||
} else {
|
||||
log.Printf("Failed to initialize email communicator: %v", err)
|
||||
}
|
||||
if gotifyHandler, err := gotify.NewClient(); err == nil {
|
||||
comm.communicators["gotify"] = gotifyHandler
|
||||
comm.fallbackCommunicators = append(comm.fallbackCommunicators, "gotify")
|
||||
} else {
|
||||
log.Printf("Failed to initialize gotify communicator: %v", err)
|
||||
}
|
||||
|
||||
return comm
|
||||
}
|
33
internal/util/slice.go
Normal file
33
internal/util/slice.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package util
|
||||
|
||||
// Contain checks if an element is in a slice.
|
||||
func Contain[T comparable](a []T, x T) bool {
|
||||
for _, n := range a {
|
||||
if x == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Unique returns a slice with all duplicate elements removed.
|
||||
func Unique[T comparable](a []T) []T {
|
||||
var result []T
|
||||
for _, e := range a {
|
||||
if !Contain(result, e) {
|
||||
result = append(result, e)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// AntiJOIN returns a slice with all elements in a that are not in b.
|
||||
func AntiJoin[T comparable](a []T, b []T) []T {
|
||||
var result []T
|
||||
for _, e := range a {
|
||||
if !Contain(b, e) {
|
||||
result = append(result, e)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -23,7 +23,7 @@ import (
|
|||
"github.com/labstack/echo/v4/middleware"
|
||||
)
|
||||
|
||||
func Init(hostname string, comm *comm.CommProvider, database db.DB) {
|
||||
func Init(hostname string, comm *comm.Communicator, database db.DB) {
|
||||
e := echo.New()
|
||||
|
||||
webroot := config.Config().WebRoot
|
||||
|
|
Loading…
Reference in a new issue