rename comm interfaces

This commit is contained in:
ゆめ 2022-11-13 01:17:42 -05:00
parent 0fb7171198
commit 4f9734d615
9 changed files with 200 additions and 105 deletions

View file

@ -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

View 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
}

View file

@ -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,

View file

@ -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)
}

View file

@ -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{}
}

View 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
}

View file

@ -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
View 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
}

View file

@ -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