imap daemon
This commit is contained in:
parent
5636958dc9
commit
c031bef006
|
@ -0,0 +1,79 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.ewintr.nl/go-kit/log"
|
||||||
|
"git.ewintr.nl/gte/internal/process"
|
||||||
|
"git.ewintr.nl/gte/internal/task"
|
||||||
|
"git.ewintr.nl/gte/pkg/msend"
|
||||||
|
"git.ewintr.nl/gte/pkg/mstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logger := log.New(os.Stdout)
|
||||||
|
logger.Info("started")
|
||||||
|
|
||||||
|
msgStore := mstore.NewIMAP(&mstore.IMAPConfig{
|
||||||
|
IMAPURL: os.Getenv("IMAP_URL"),
|
||||||
|
IMAPUsername: os.Getenv("IMAP_USERNAME"),
|
||||||
|
IMAPPassword: os.Getenv("IMAP_PASSWORD"),
|
||||||
|
})
|
||||||
|
msgSender := msend.NewSSLSMTP(&msend.SSLSMTPConfig{
|
||||||
|
URL: os.Getenv("SMTP_URL"),
|
||||||
|
Username: os.Getenv("SMTP_USERNAME"),
|
||||||
|
Password: os.Getenv("SMTP_PASSWORD"),
|
||||||
|
From: os.Getenv("SMTP_FROM"),
|
||||||
|
To: os.Getenv("SMTP_TO"),
|
||||||
|
})
|
||||||
|
repo := task.NewRepository(msgStore)
|
||||||
|
disp := task.NewDispatcher(msgSender)
|
||||||
|
|
||||||
|
inboxProc := process.NewInbox(repo)
|
||||||
|
recurProc := process.NewRecur(repo, disp, 6)
|
||||||
|
|
||||||
|
go Run(inboxProc, recurProc, logger)
|
||||||
|
|
||||||
|
done := make(chan os.Signal)
|
||||||
|
signal.Notify(done, os.Interrupt)
|
||||||
|
<-done
|
||||||
|
logger.Info("stopped")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(inboxProc *process.Inbox, recurProc *process.Recur, logger log.Logger) {
|
||||||
|
logger = logger.WithField("func", "run")
|
||||||
|
inboxTicker := time.NewTicker(10 * time.Second)
|
||||||
|
recurTicker := time.NewTicker(time.Hour)
|
||||||
|
oldToday := task.Today
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-inboxTicker.C:
|
||||||
|
result, err := inboxProc.Process()
|
||||||
|
if err != nil {
|
||||||
|
logger.WithErr(err).Error("failed processing inbox")
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
logger.WithField("count", result.Count).Info("finished processing inbox")
|
||||||
|
case <-recurTicker.C:
|
||||||
|
year, month, day := time.Now().Date()
|
||||||
|
newToday := task.NewDate(year, int(month), day)
|
||||||
|
if oldToday.Equal(newToday) {
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
oldToday = newToday
|
||||||
|
result, err := recurProc.Process()
|
||||||
|
if err != nil {
|
||||||
|
logger.WithErr(err).Error("failed generating recurring tasks")
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
logger.WithField("count", result.Count).Info("finished generating recurring tasks")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,51 +1,52 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"git.ewintr.nl/go-kit/log"
|
||||||
|
"git.ewintr.nl/gte/internal/process"
|
||||||
"git.ewintr.nl/gte/internal/task"
|
"git.ewintr.nl/gte/internal/task"
|
||||||
|
"git.ewintr.nl/gte/pkg/msend"
|
||||||
"git.ewintr.nl/gte/pkg/mstore"
|
"git.ewintr.nl/gte/pkg/mstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config := &mstore.ImapConfiguration{
|
logger := log.New(os.Stdout).WithField("cmd", "generate-recurring")
|
||||||
ImapUrl: os.Getenv("IMAP_URL"),
|
IMAPConfig := &mstore.IMAPConfig{
|
||||||
ImapUsername: os.Getenv("IMAP_USERNAME"),
|
IMAPURL: os.Getenv("IMAP_URL"),
|
||||||
ImapPassword: os.Getenv("IMAP_PASSWORD"),
|
IMAPUsername: os.Getenv("IMAP_USERNAME"),
|
||||||
|
IMAPPassword: os.Getenv("IMAP_PASSWORD"),
|
||||||
}
|
}
|
||||||
if !config.Valid() {
|
msgStore := mstore.NewIMAP(IMAPConfig)
|
||||||
log.Fatal("please set IMAP_USER, IMAP_PASSWORD, etc environment variables")
|
|
||||||
|
SMTPConfig := &msend.SSLSMTPConfig{
|
||||||
|
URL: os.Getenv("SMTP_URL"),
|
||||||
|
Username: os.Getenv("SMTP_USERNAME"),
|
||||||
|
Password: os.Getenv("SMTP_PASSWORD"),
|
||||||
|
From: os.Getenv("SMTP_FROM"),
|
||||||
|
To: os.Getenv("SMTP_TO"),
|
||||||
}
|
}
|
||||||
|
if !SMTPConfig.Valid() {
|
||||||
|
logger.Error("please set SMTP_URL, SMTP_USERNAME, etc environment variables")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
mailSend := msend.NewSSLSMTP(SMTPConfig)
|
||||||
|
|
||||||
daysAhead, err := strconv.Atoi(os.Getenv("GTE_DAYS_AHEAD"))
|
daysAhead, err := strconv.Atoi(os.Getenv("GTE_DAYS_AHEAD"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
daysAhead = 0
|
daysAhead = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
mailStore, err := mstore.ImapConnect(config)
|
taskRepo := task.NewRepository(msgStore)
|
||||||
|
taskDisp := task.NewDispatcher(mailSend)
|
||||||
|
|
||||||
|
recur := process.NewRecur(taskRepo, taskDisp, daysAhead)
|
||||||
|
result, err := recur.Process()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
logger.WithErr(err).Error("unable to process recurring")
|
||||||
}
|
os.Exit(1)
|
||||||
defer mailStore.Disconnect()
|
|
||||||
|
|
||||||
taskRepo := task.NewRepository(mailStore)
|
|
||||||
tasks, err := taskRepo.FindAll(task.FOLDER_RECURRING)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
rDate := task.Today.AddDays(daysAhead)
|
|
||||||
for _, t := range tasks {
|
|
||||||
if t.RecursOn(rDate) {
|
|
||||||
subject, body, err := t.CreateDueMessage(rDate)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := mailStore.Add(task.FOLDER_PLANNED, subject, body); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.WithField("count", result.Count).Info("finished generating recurring tasks")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,28 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"git.ewintr.nl/go-kit/log"
|
||||||
|
"git.ewintr.nl/gte/internal/process"
|
||||||
"git.ewintr.nl/gte/internal/task"
|
"git.ewintr.nl/gte/internal/task"
|
||||||
"git.ewintr.nl/gte/pkg/mstore"
|
"git.ewintr.nl/gte/pkg/mstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config := &mstore.ImapConfiguration{
|
logger := log.New(os.Stdout).WithField("cmd", "process-inbox")
|
||||||
ImapUrl: os.Getenv("IMAP_URL"),
|
config := &mstore.IMAPConfig{
|
||||||
ImapUsername: os.Getenv("IMAP_USERNAME"),
|
IMAPURL: os.Getenv("IMAP_URL"),
|
||||||
ImapPassword: os.Getenv("IMAP_PASSWORD"),
|
IMAPUsername: os.Getenv("IMAP_USERNAME"),
|
||||||
}
|
IMAPPassword: os.Getenv("IMAP_PASSWORD"),
|
||||||
if !config.Valid() {
|
|
||||||
log.Fatal("please set IMAP_USER, IMAP_PASSWORD, etc environment variables")
|
|
||||||
}
|
}
|
||||||
|
msgStore := mstore.NewIMAP(config)
|
||||||
|
|
||||||
mailStore, err := mstore.ImapConnect(config)
|
inboxProcessor := process.NewInbox(task.NewRepository(msgStore))
|
||||||
|
result, err := inboxProcessor.Process()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
logger.WithErr(err).Error("unable to process inbox")
|
||||||
}
|
os.Exit(1)
|
||||||
defer mailStore.Disconnect()
|
|
||||||
|
|
||||||
taskRepo := task.NewRepository(mailStore)
|
|
||||||
tasks, err := taskRepo.FindAll(task.FOLDER_INBOX)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
var cleanupNeeded bool
|
|
||||||
for _, t := range tasks {
|
|
||||||
if t.Dirty {
|
|
||||||
if err := taskRepo.Update(t); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
cleanupNeeded = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cleanupNeeded {
|
|
||||||
if err := taskRepo.CleanUp(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
logger.WithField("count", result.Count).Info("finished processing inbox")
|
||||||
}
|
}
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -3,7 +3,7 @@ module git.ewintr.nl/gte
|
||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.ewintr.nl/go-kit v0.0.0-20210509123609-19e474005502
|
git.ewintr.nl/go-kit v0.0.0-20210513091124-da7006c2c242
|
||||||
github.com/emersion/go-imap v1.1.0
|
github.com/emersion/go-imap v1.1.0
|
||||||
github.com/emersion/go-message v0.14.1
|
github.com/emersion/go-message v0.14.1
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -1,7 +1,9 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
git.ewintr.nl/go-kit v0.0.0-20210509123609-19e474005502 h1:h3PYDz6uWAz2mBPOFShhkZv7SapfLWXcBDsmHqFCq5w=
|
git.ewintr.nl/go-kit v0.0.0-20210513084754-6c0524f3de86 h1:jVP4muIBqQ5poAuOlDgW/PhYscSnRHdqEqX1WfAK++A=
|
||||||
git.ewintr.nl/go-kit v0.0.0-20210509123609-19e474005502/go.mod h1:eYANz1nepfc6lHxa9UcNZFvLaezBrihttH/Pdc5+0Vk=
|
git.ewintr.nl/go-kit v0.0.0-20210513084754-6c0524f3de86/go.mod h1:eYANz1nepfc6lHxa9UcNZFvLaezBrihttH/Pdc5+0Vk=
|
||||||
|
git.ewintr.nl/go-kit v0.0.0-20210513091124-da7006c2c242 h1:9UIbgqTOIot2rAXqVWhd0Q9MyWru1kIzr52HPWpCCTM=
|
||||||
|
git.ewintr.nl/go-kit v0.0.0-20210513091124-da7006c2c242/go.mod h1:eYANz1nepfc6lHxa9UcNZFvLaezBrihttH/Pdc5+0Vk=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||||
|
@ -65,11 +67,14 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
|
||||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
@ -224,6 +229,7 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
@ -237,7 +243,6 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
@ -310,6 +315,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f h1:68K/z8GLUxV76xGSqwTWw2gyk/jwn79LUL43rES2g8o=
|
||||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package process
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.ewintr.nl/gte/internal/task"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInboxProcess = errors.New("could not process inbox")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Inbox struct {
|
||||||
|
taskRepo *task.TaskRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboxResult struct {
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInbox(repo *task.TaskRepo) *Inbox {
|
||||||
|
return &Inbox{
|
||||||
|
taskRepo: repo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inbox *Inbox) Process() (*InboxResult, error) {
|
||||||
|
tasks, err := inbox.taskRepo.FindAll(task.FOLDER_INBOX)
|
||||||
|
if err != nil {
|
||||||
|
return &InboxResult{}, fmt.Errorf("%w: %v", ErrInboxProcess, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cleanupNeeded bool
|
||||||
|
for _, t := range tasks {
|
||||||
|
if t.Dirty {
|
||||||
|
if err := inbox.taskRepo.Update(t); err != nil {
|
||||||
|
return &InboxResult{}, fmt.Errorf("%w: %v", ErrInboxProcess, err)
|
||||||
|
}
|
||||||
|
cleanupNeeded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cleanupNeeded {
|
||||||
|
if err := inbox.taskRepo.CleanUp(); err != nil {
|
||||||
|
return &InboxResult{}, fmt.Errorf("%w: %v", ErrInboxProcess, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &InboxResult{
|
||||||
|
Count: len(tasks),
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
package process_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.ewintr.nl/go-kit/test"
|
||||||
|
"git.ewintr.nl/gte/internal/process"
|
||||||
|
"git.ewintr.nl/gte/internal/task"
|
||||||
|
"git.ewintr.nl/gte/pkg/mstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInboxProcess(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
messages map[string][]*mstore.Message
|
||||||
|
expResult *process.InboxResult
|
||||||
|
expMsgs map[string][]*mstore.Message
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
messages: map[string][]*mstore.Message{
|
||||||
|
task.FOLDER_INBOX: {},
|
||||||
|
},
|
||||||
|
expResult: &process.InboxResult{},
|
||||||
|
expMsgs: map[string][]*mstore.Message{
|
||||||
|
task.FOLDER_INBOX: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all flavors",
|
||||||
|
messages: map[string][]*mstore.Message{
|
||||||
|
task.FOLDER_INBOX: {
|
||||||
|
{
|
||||||
|
Subject: "to new",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Subject: "to recurring",
|
||||||
|
Body: "recur: 2021-05-14, daily\nid: xxx-xxx\nversion: 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Subject: "to planned",
|
||||||
|
Body: "due: 2021-05-14\nid: xxx-xxx\nversion: 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Subject: "to unplanned",
|
||||||
|
Body: "id: xxx-xxx\nversion: 1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expResult: &process.InboxResult{
|
||||||
|
Count: 4,
|
||||||
|
},
|
||||||
|
expMsgs: map[string][]*mstore.Message{
|
||||||
|
task.FOLDER_INBOX: {},
|
||||||
|
task.FOLDER_NEW: {{Subject: "to new"}},
|
||||||
|
task.FOLDER_RECURRING: {{Subject: "to recurring"}},
|
||||||
|
task.FOLDER_PLANNED: {{Subject: "2021-05-14 (friday) - to planned"}},
|
||||||
|
task.FOLDER_UNPLANNED: {{Subject: "to unplanned"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cleanup",
|
||||||
|
messages: map[string][]*mstore.Message{
|
||||||
|
task.FOLDER_INBOX: {{
|
||||||
|
Subject: "new version",
|
||||||
|
Body: "id: xxx-xxx\nversion: 3",
|
||||||
|
}},
|
||||||
|
task.FOLDER_UNPLANNED: {{
|
||||||
|
Subject: "old version",
|
||||||
|
Body: "id: xxx-xxx\nversion: 3",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expResult: &process.InboxResult{
|
||||||
|
Count: 1,
|
||||||
|
},
|
||||||
|
expMsgs: map[string][]*mstore.Message{
|
||||||
|
task.FOLDER_INBOX: {},
|
||||||
|
task.FOLDER_UNPLANNED: {{Subject: "new version"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cleanup version conflict",
|
||||||
|
messages: map[string][]*mstore.Message{
|
||||||
|
task.FOLDER_INBOX: {{
|
||||||
|
Subject: "new version",
|
||||||
|
Body: "id: xxx-xxx\nversion: 3",
|
||||||
|
}},
|
||||||
|
task.FOLDER_UNPLANNED: {{
|
||||||
|
Subject: "not really old version",
|
||||||
|
Body: "id: xxx-xxx\nversion: 5",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expResult: &process.InboxResult{
|
||||||
|
Count: 1,
|
||||||
|
},
|
||||||
|
expMsgs: map[string][]*mstore.Message{
|
||||||
|
task.FOLDER_INBOX: {},
|
||||||
|
task.FOLDER_UNPLANNED: {{Subject: "not really old version"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
mstorer, err := mstore.NewMemory([]string{
|
||||||
|
task.FOLDER_INBOX,
|
||||||
|
task.FOLDER_NEW,
|
||||||
|
task.FOLDER_RECURRING,
|
||||||
|
task.FOLDER_PLANNED,
|
||||||
|
task.FOLDER_UNPLANNED,
|
||||||
|
})
|
||||||
|
test.OK(t, err)
|
||||||
|
for folder, messages := range tc.messages {
|
||||||
|
for _, m := range messages {
|
||||||
|
test.OK(t, mstorer.Add(folder, m.Subject, m.Body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inboxProc := process.NewInbox(task.NewRepository(mstorer))
|
||||||
|
actResult, err := inboxProc.Process()
|
||||||
|
|
||||||
|
test.OK(t, err)
|
||||||
|
test.Equals(t, tc.expResult, actResult)
|
||||||
|
for folder, expMessages := range tc.expMsgs {
|
||||||
|
actMessages, err := mstorer.Messages(folder)
|
||||||
|
test.OK(t, err)
|
||||||
|
test.Equals(t, len(expMessages), len(actMessages))
|
||||||
|
if len(expMessages) == 0 {
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
test.Equals(t, expMessages[0].Subject, actMessages[0].Subject)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package process
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.ewintr.nl/gte/internal/task"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrRecurProcess = errors.New("could not generate tasks from recurrer")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Recur struct {
|
||||||
|
taskRepo *task.TaskRepo
|
||||||
|
taskDispatcher *task.Dispatcher
|
||||||
|
daysAhead int
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecurResult struct {
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecur(repo *task.TaskRepo, disp *task.Dispatcher, daysAhead int) *Recur {
|
||||||
|
return &Recur{
|
||||||
|
taskRepo: repo,
|
||||||
|
taskDispatcher: disp,
|
||||||
|
daysAhead: daysAhead,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (recur *Recur) Process() (*RecurResult, error) {
|
||||||
|
tasks, err := recur.taskRepo.FindAll(task.FOLDER_RECURRING)
|
||||||
|
if err != nil {
|
||||||
|
return &RecurResult{}, fmt.Errorf("%w: %v", ErrRecurProcess, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rDate := task.Today.AddDays(recur.daysAhead)
|
||||||
|
var count int
|
||||||
|
for _, t := range tasks {
|
||||||
|
if t.RecursOn(rDate) {
|
||||||
|
newTask, err := t.GenerateFromRecurrer(rDate)
|
||||||
|
if err != nil {
|
||||||
|
return &RecurResult{}, fmt.Errorf("%w: %v", ErrRecurProcess, err)
|
||||||
|
}
|
||||||
|
if err := recur.taskDispatcher.Dispatch(newTask); err != nil {
|
||||||
|
return &RecurResult{}, fmt.Errorf("%w: %v", ErrRecurProcess, err)
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RecurResult{
|
||||||
|
Count: count,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package process_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.ewintr.nl/go-kit/test"
|
||||||
|
"git.ewintr.nl/gte/internal/process"
|
||||||
|
"git.ewintr.nl/gte/internal/task"
|
||||||
|
"git.ewintr.nl/gte/pkg/msend"
|
||||||
|
"git.ewintr.nl/gte/pkg/mstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRecurProcess(t *testing.T) {
|
||||||
|
task.Today = task.NewDate(2021, 5, 14)
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
recurMsgs []*mstore.Message
|
||||||
|
expResult *process.RecurResult
|
||||||
|
expMsgs []*msend.Message
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
expResult: &process.RecurResult{},
|
||||||
|
expMsgs: []*msend.Message{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one of two recurring",
|
||||||
|
recurMsgs: []*mstore.Message{
|
||||||
|
{
|
||||||
|
Subject: "not recurring",
|
||||||
|
Body: "recur: 2021-05-20, daily\nid: xxx-xxx\nversion: 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Subject: "recurring",
|
||||||
|
Body: "recur: 2021-05-10, daily\nid: xxx-xxx\nversion: 1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expResult: &process.RecurResult{
|
||||||
|
Count: 1,
|
||||||
|
},
|
||||||
|
expMsgs: []*msend.Message{
|
||||||
|
{Subject: "2021-05-15 (saturday) - recurring"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
mstorer, err := mstore.NewMemory([]string{
|
||||||
|
task.FOLDER_INBOX,
|
||||||
|
task.FOLDER_NEW,
|
||||||
|
task.FOLDER_RECURRING,
|
||||||
|
task.FOLDER_PLANNED,
|
||||||
|
task.FOLDER_UNPLANNED,
|
||||||
|
})
|
||||||
|
test.OK(t, err)
|
||||||
|
for _, m := range tc.recurMsgs {
|
||||||
|
test.OK(t, mstorer.Add(task.FOLDER_RECURRING, m.Subject, m.Body))
|
||||||
|
}
|
||||||
|
msender := msend.NewMemory()
|
||||||
|
|
||||||
|
recurProc := process.NewRecur(task.NewRepository(mstorer), task.NewDispatcher(msender), 1)
|
||||||
|
actResult, err := recurProc.Process()
|
||||||
|
test.OK(t, err)
|
||||||
|
test.Equals(t, tc.expResult, actResult)
|
||||||
|
for i, expMsg := range tc.expMsgs {
|
||||||
|
test.Equals(t, expMsg.Subject, msender.Messages[i].Subject)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package task
|
||||||
|
|
||||||
|
import "git.ewintr.nl/gte/pkg/msend"
|
||||||
|
|
||||||
|
type Dispatcher struct {
|
||||||
|
msender msend.MSender
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDispatcher(msender msend.MSender) *Dispatcher {
|
||||||
|
return &Dispatcher{
|
||||||
|
msender: msender,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dispatcher) Dispatch(t *Task) error {
|
||||||
|
return d.msender.Send(&msend.Message{
|
||||||
|
Subject: t.FormatSubject(),
|
||||||
|
Body: t.FormatBody(),
|
||||||
|
})
|
||||||
|
}
|
|
@ -52,8 +52,8 @@ func (tr *TaskRepo) Update(t *Task) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add new
|
// add new
|
||||||
if err := tr.mstore.Add(t.Folder, t.FormatSubject(), t.FormatBody()); err != nil {
|
if err := tr.Add(t); err != nil {
|
||||||
return fmt.Errorf("%w: %s", ErrMStoreError, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove old
|
// remove old
|
||||||
|
@ -66,6 +66,18 @@ func (tr *TaskRepo) Update(t *Task) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tr *TaskRepo) Add(t *Task) error {
|
||||||
|
if t == nil {
|
||||||
|
return ErrInvalidTask
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tr.mstore.Add(t.Folder, t.FormatSubject(), t.FormatBody()); err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrMStoreError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup removes older versions of tasks
|
// Cleanup removes older versions of tasks
|
||||||
func (tr *TaskRepo) CleanUp() error {
|
func (tr *TaskRepo) CleanUp() error {
|
||||||
// loop through folders, get all task version info
|
// loop through folders, get all task version info
|
||||||
|
|
|
@ -261,20 +261,18 @@ func (t *Task) RecursOn(date Date) bool {
|
||||||
return t.Recur.RecursOn(date)
|
return t.Recur.RecursOn(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) CreateDueMessage(date Date) (string, string, error) {
|
func (t *Task) GenerateFromRecurrer(date Date) (*Task, error) {
|
||||||
if !t.IsRecurrer() {
|
if !t.IsRecurrer() || !t.RecursOn(date) {
|
||||||
return "", "", ErrTaskIsNotRecurring
|
return &Task{}, ErrTaskIsNotRecurring
|
||||||
}
|
}
|
||||||
|
|
||||||
tempTask := &Task{
|
return &Task{
|
||||||
Id: uuid.New().String(),
|
Id: uuid.New().String(),
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Action: t.Action,
|
Action: t.Action,
|
||||||
Project: t.Project,
|
Project: t.Project,
|
||||||
Due: date,
|
Due: date,
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
return tempTask.FormatSubject(), tempTask.FormatBody(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func FieldFromBody(field, body string) (string, bool) {
|
func FieldFromBody(field, body string) (string, bool) {
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package msend
|
||||||
|
|
||||||
|
type Memory struct {
|
||||||
|
Messages []*Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMemory() *Memory {
|
||||||
|
return &Memory{
|
||||||
|
Messages: []*Message{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mem *Memory) Send(msg *Message) error {
|
||||||
|
mem.Messages = append(mem.Messages, msg)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package msend_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.ewintr.nl/go-kit/test"
|
||||||
|
"git.ewintr.nl/gte/pkg/msend"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMemorySend(t *testing.T) {
|
||||||
|
mem := msend.NewMemory()
|
||||||
|
test.Equals(t, []*msend.Message{}, mem.Messages)
|
||||||
|
|
||||||
|
msg1 := &msend.Message{Subject: "sub1", Body: "body1"}
|
||||||
|
test.OK(t, mem.Send(msg1))
|
||||||
|
test.Equals(t, []*msend.Message{msg1}, mem.Messages)
|
||||||
|
|
||||||
|
msg2 := &msend.Message{Subject: "sub2", Body: "body2"}
|
||||||
|
test.OK(t, mem.Send(msg2))
|
||||||
|
test.Equals(t, []*msend.Message{msg1, msg2}, mem.Messages)
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package msend
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSendFail = errors.New("could not send message")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Subject string
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MSender interface {
|
||||||
|
Send(msg *Message) error
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
package msend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/mail"
|
||||||
|
"net/smtp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSMTPInvalidConfig = errors.New("invalid smtp configuration")
|
||||||
|
ErrSMTPConnectionFailed = errors.New("connection to smtp server failed")
|
||||||
|
ErrSendMessageFailed = errors.New("could not send message")
|
||||||
|
)
|
||||||
|
|
||||||
|
type SSLSMTPConfig struct {
|
||||||
|
URL string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
From string
|
||||||
|
To string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ssc *SSLSMTPConfig) Valid() bool {
|
||||||
|
if _, _, err := net.SplitHostPort(ssc.URL); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssc.Username != "" && ssc.Password != "" && ssc.To != "" && ssc.From != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSLSMTP struct {
|
||||||
|
config *SSLSMTPConfig
|
||||||
|
client *smtp.Client
|
||||||
|
connected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSSLSMTP(config *SSLSMTPConfig) *SSLSMTP {
|
||||||
|
return &SSLSMTP{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSLSMTP) Connect() error {
|
||||||
|
if !s.config.Valid() {
|
||||||
|
return ErrSMTPInvalidConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
host, _, _ := net.SplitHostPort(s.config.URL)
|
||||||
|
auth := smtp.PlainAuth("", s.config.Username, s.config.Password, host)
|
||||||
|
conn, err := tls.Dial("tcp", s.config.URL, &tls.Config{ServerName: host})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrSMTPConnectionFailed, err)
|
||||||
|
}
|
||||||
|
client, err := smtp.NewClient(conn, host)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrSMTPConnectionFailed, err)
|
||||||
|
}
|
||||||
|
if err := client.Auth(auth); err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrSMTPConnectionFailed, err)
|
||||||
|
}
|
||||||
|
s.client = client
|
||||||
|
s.connected = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSLSMTP) Close() error {
|
||||||
|
if !s.connected {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.client.Quit(); err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrSMTPConnectionFailed, err)
|
||||||
|
}
|
||||||
|
s.connected = false
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSLSMTP) Send(msg *Message) error {
|
||||||
|
if err := s.Connect(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
from := mail.Address{
|
||||||
|
Name: "gte",
|
||||||
|
Address: s.config.From,
|
||||||
|
}
|
||||||
|
to := mail.Address{
|
||||||
|
Name: "todo",
|
||||||
|
Address: s.config.To,
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := make(map[string]string)
|
||||||
|
headers["From"] = from.String()
|
||||||
|
headers["To"] = to.String()
|
||||||
|
headers["Subject"] = msg.Subject
|
||||||
|
|
||||||
|
message := ""
|
||||||
|
for k, v := range headers {
|
||||||
|
message += fmt.Sprintf("%s: %s\r\n", k, v)
|
||||||
|
}
|
||||||
|
message += fmt.Sprintf("\r\n%s", msg.Body)
|
||||||
|
|
||||||
|
if err := s.client.Mail(s.config.From); err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrSendMessageFailed, err)
|
||||||
|
}
|
||||||
|
if err := s.client.Rcpt(s.config.To); err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrSendMessageFailed, err)
|
||||||
|
}
|
||||||
|
wc, err := s.client.Data()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrSendMessageFailed, err)
|
||||||
|
}
|
||||||
|
if _, err := wc.Write([]byte(message)); err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrSendMessageFailed, err)
|
||||||
|
}
|
||||||
|
if err := wc.Close(); err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrSendMessageFailed, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package mstore
|
package mstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -12,71 +13,97 @@ import (
|
||||||
"github.com/emersion/go-message/mail"
|
"github.com/emersion/go-message/mail"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Body struct {
|
var (
|
||||||
|
ErrIMAPInvalidConfig = errors.New("invalid imap configuration")
|
||||||
|
ErrIMAPConnFailure = errors.New("could not connect with imap")
|
||||||
|
ErrIMAPNotConnected = errors.New("unable to perform, not connected to imap")
|
||||||
|
ErrIMAPServerProblem = errors.New("imap server was unable to perform operation")
|
||||||
|
)
|
||||||
|
|
||||||
|
type IMAPBody struct {
|
||||||
reader io.Reader
|
reader io.Reader
|
||||||
length int
|
length int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBody(msg string) *Body {
|
func NewIMAPBody(msg string) *IMAPBody {
|
||||||
|
return &IMAPBody{
|
||||||
return &Body{
|
|
||||||
reader: strings.NewReader(msg),
|
reader: strings.NewReader(msg),
|
||||||
length: len([]byte(msg)),
|
length: len([]byte(msg)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Body) Read(p []byte) (int, error) {
|
func (b *IMAPBody) Read(p []byte) (int, error) {
|
||||||
return b.reader.Read(p)
|
return b.reader.Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Body) Len() int {
|
func (b *IMAPBody) Len() int {
|
||||||
return b.length
|
return b.length
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImapConfiguration struct {
|
type IMAPConfig struct {
|
||||||
ImapUrl string
|
IMAPURL string
|
||||||
ImapUsername string
|
IMAPUsername string
|
||||||
ImapPassword string
|
IMAPPassword string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (esc *ImapConfiguration) Valid() bool {
|
func (esc *IMAPConfig) Valid() bool {
|
||||||
if esc.ImapUrl == "" {
|
if esc.IMAPURL == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if esc.ImapUsername == "" || esc.ImapPassword == "" {
|
if esc.IMAPUsername == "" || esc.IMAPPassword == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type Imap struct {
|
type IMAP struct {
|
||||||
imap *client.Client
|
config *IMAPConfig
|
||||||
|
connected bool
|
||||||
|
client *client.Client
|
||||||
mboxStatus *imap.MailboxStatus
|
mboxStatus *imap.MailboxStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImapConnect(conf *ImapConfiguration) (*Imap, error) {
|
func NewIMAP(config *IMAPConfig) *IMAP {
|
||||||
imap, err := client.DialTLS(conf.ImapUrl, nil)
|
return &IMAP{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *IMAP) Connect() error {
|
||||||
|
if !im.config.Valid() {
|
||||||
|
return ErrIMAPInvalidConfig
|
||||||
|
}
|
||||||
|
if im.connected {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := client.DialTLS(im.config.IMAPURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Imap{}, err
|
return fmt.Errorf("%w: %v", ErrIMAPConnFailure, err)
|
||||||
}
|
}
|
||||||
if err := imap.Login(conf.ImapUsername, conf.ImapPassword); err != nil {
|
if err := cl.Login(im.config.IMAPUsername, im.config.IMAPPassword); err != nil {
|
||||||
return &Imap{}, err
|
return fmt.Errorf("%w: %v", ErrIMAPConnFailure, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Imap{
|
im.client = cl
|
||||||
imap: imap,
|
im.connected = true
|
||||||
}, nil
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Imap) Disconnect() {
|
func (im *IMAP) Close() {
|
||||||
es.imap.Logout()
|
im.client.Logout()
|
||||||
|
im.connected = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Imap) Folders() ([]string, error) {
|
func (im *IMAP) Folders() ([]string, error) {
|
||||||
|
im.Connect()
|
||||||
|
defer im.Close()
|
||||||
|
|
||||||
boxes, done := make(chan *imap.MailboxInfo), make(chan error)
|
boxes, done := make(chan *imap.MailboxInfo), make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
done <- es.imap.List("", "*", boxes)
|
done <- im.client.List("", "*", boxes)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
folders := []string{}
|
folders := []string{}
|
||||||
|
@ -91,28 +118,35 @@ func (es *Imap) Folders() ([]string, error) {
|
||||||
return folders, nil
|
return folders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Imap) selectFolder(folder string) error {
|
func (im *IMAP) selectFolder(folder string) error {
|
||||||
status, err := es.imap.Select(folder, false)
|
if !im.connected {
|
||||||
if err != nil {
|
return ErrIMAPNotConnected
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
es.mboxStatus = status
|
status, err := im.client.Select(folder, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w, %v", ErrIMAPServerProblem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
im.mboxStatus = status
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Imap) Messages(folder string) ([]*Message, error) {
|
func (im *IMAP) Messages(folder string) ([]*Message, error) {
|
||||||
if err := es.selectFolder(folder); err != nil {
|
im.Connect()
|
||||||
|
defer im.Close()
|
||||||
|
|
||||||
|
if err := im.selectFolder(folder); err != nil {
|
||||||
return []*Message{}, err
|
return []*Message{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if es.mboxStatus.Messages == 0 {
|
if im.mboxStatus.Messages == 0 {
|
||||||
return []*Message{}, nil
|
return []*Message{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
seqset := new(imap.SeqSet)
|
seqset := new(imap.SeqSet)
|
||||||
seqset.AddRange(uint32(1), es.mboxStatus.Messages)
|
seqset.AddRange(uint32(1), im.mboxStatus.Messages)
|
||||||
|
|
||||||
// Get the whole message body
|
// Get the whole message body
|
||||||
section := &imap.BodySectionName{}
|
section := &imap.BodySectionName{}
|
||||||
|
@ -120,7 +154,7 @@ func (es *Imap) Messages(folder string) ([]*Message, error) {
|
||||||
|
|
||||||
imsg, done := make(chan *imap.Message), make(chan error)
|
imsg, done := make(chan *imap.Message), make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
done <- es.imap.Fetch(seqset, items, imsg)
|
done <- im.client.Fetch(seqset, items, imsg)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
messages := []*Message{}
|
messages := []*Message{}
|
||||||
|
@ -169,30 +203,39 @@ func (es *Imap) Messages(folder string) ([]*Message, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := <-done; err != nil {
|
if err := <-done; err != nil {
|
||||||
return []*Message{}, err
|
return []*Message{}, fmt.Errorf("%w: %v", ErrIMAPServerProblem, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Imap) Add(folder, subject, body string) error {
|
func (im *IMAP) Add(folder, subject, body string) error {
|
||||||
|
im.Connect()
|
||||||
|
defer im.Close()
|
||||||
|
|
||||||
msgStr := fmt.Sprintf(`From: todo <mstore@erikwinter.nl>
|
msgStr := fmt.Sprintf(`From: todo <mstore@erikwinter.nl>
|
||||||
Date: %s
|
Date: %s
|
||||||
Subject: %s
|
Subject: %s
|
||||||
|
|
||||||
%s`, time.Now().Format(time.RFC822Z), subject, body)
|
%s`, time.Now().Format(time.RFC822Z), subject, body)
|
||||||
|
|
||||||
msg := NewBody(msgStr)
|
msg := NewIMAPBody(msgStr)
|
||||||
|
|
||||||
return es.imap.Append(folder, nil, time.Time{}, imap.Literal(msg))
|
if err := im.client.Append(folder, nil, time.Time{}, imap.Literal(msg)); err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrIMAPServerProblem, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Imap) Remove(msg *Message) error {
|
func (im *IMAP) Remove(msg *Message) error {
|
||||||
if msg == nil || !msg.Valid() {
|
if msg == nil || !msg.Valid() {
|
||||||
return ErrInvalidMessage
|
return ErrInvalidMessage
|
||||||
}
|
}
|
||||||
|
im.Connect()
|
||||||
|
defer im.Close()
|
||||||
|
|
||||||
if err := es.selectFolder(msg.Folder); err != nil {
|
if err := im.selectFolder(msg.Folder); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,14 +243,14 @@ func (es *Imap) Remove(msg *Message) error {
|
||||||
seqset := new(imap.SeqSet)
|
seqset := new(imap.SeqSet)
|
||||||
seqset.AddRange(msg.Uid, msg.Uid)
|
seqset.AddRange(msg.Uid, msg.Uid)
|
||||||
storeItem := imap.FormatFlagsOp(imap.SetFlags, true)
|
storeItem := imap.FormatFlagsOp(imap.SetFlags, true)
|
||||||
err := es.imap.UidStore(seqset, storeItem, imap.FormatStringList([]string{imap.DeletedFlag}), nil)
|
err := im.client.UidStore(seqset, storeItem, imap.FormatStringList([]string{imap.DeletedFlag}), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("%w: %v", ErrIMAPServerProblem, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// expunge box
|
// expunge box
|
||||||
if err := es.imap.Expunge(nil); err != nil {
|
if err := im.client.Expunge(nil); err != nil {
|
||||||
return err
|
return fmt.Errorf("%w: %v", ErrIMAPServerProblem, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Reference in New Issue