Save before recoding imap to use websockets

This commit is contained in:
Melon 2023-09-11 14:55:12 +01:00
parent dfc74925cd
commit cd9a6074d3
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
7 changed files with 147 additions and 28 deletions

8
.idea/.gitignore vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

View File

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="ASK" />
<option name="description" value="" />
</component>
<component name="MarkdownSettingsMigration"> <component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" /> <option name="stateVersion" value="1" />
</component> </component>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="PROJECT" dialect="MySQL" />
</component>
</project>

View File

@ -3,7 +3,8 @@ package api
import ( import (
"encoding/json" "encoding/json"
"github.com/1f349/lotus/imap" "github.com/1f349/lotus/imap"
json2 "github.com/1f349/lotus/imap/json" "github.com/1f349/lotus/imap/marshal"
imap2 "github.com/emersion/go-imap"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"log" "log"
"net/http" "net/http"
@ -14,18 +15,105 @@ func SetupApiServer(listen string, auth func(callback AuthCallback) httprouter.H
r := httprouter.New() r := httprouter.New()
// === ACCOUNT === // === ACCOUNT ===
r.GET("/account", auth(func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, b AuthClaims) { r.GET("/identities", auth(func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, b AuthClaims) {
// TODO(melon): find users aliases and other account data // TODO(melon): find users aliases and other account data
})) }))
// === SMTP === // === SMTP ===
r.POST("/message", auth(MessageSender(send))) r.POST("/message", auth(MessageSender(send)))
r.Handle(http.MethodConnect, "/", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
})
// === IMAP === // === IMAP ===
type statusJson struct { type mailboxStatusJson struct {
Folder string `json:"folder"` Folder string `json:"folder"`
} }
r.GET("/status", auth(imapClient(recv, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t statusJson) { r.GET("/mailbox/status", auth(imapClient(recv, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t mailboxStatusJson) error {
status, err := cli.Status(t.Folder)
if err != nil {
return err
}
return json.NewEncoder(rw).Encode(status)
})))
type mailboxListJson struct {
Folder string `json:"folder"`
Pattern string `json:"pattern"`
}
r.GET("/mailbox/list", auth(imapClient(recv, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t mailboxListJson) error {
list, err := cli.List(t.Folder, t.Pattern)
if err != nil {
return err
}
return json.NewEncoder(rw).Encode(list)
})))
type mailboxCreateJson struct {
Name string `json:"name"`
}
r.POST("/mailbox/create", auth(imapClient(recv, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t mailboxCreateJson) error {
err := cli.Create(t.Name)
if err != nil {
return err
}
return json.NewEncoder(rw).Encode(map[string]string{"Status": "OK"})
})))
type messagesListJson struct {
Folder string `json:"folder"`
Start uint32 `json:"start"`
End uint32 `json:"end"`
Limit uint32 `json:"limit"`
}
r.GET("/list-messages", auth(imapClient(recv, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t messagesListJson) error {
messages, err := cli.Fetch(t.Folder, t.Start, t.End, t.Limit)
if err != nil {
return err
}
return json.NewEncoder(rw).Encode(marshal.MessageSliceJson(messages))
})))
type messagesSearchJson struct {
Folder string `json:"folder"`
SeqNum imap2.SeqSet
Uid imap2.SeqSet
Since time.Time
Before time.Time
SentSince time.Time
SentBefore time.Time
Body []string
Text []string
WithFlags []string
WithoutFlags []string
Larger uint32
Smaller uint32
}
r.GET("/search-messages", auth(imapClient(recv, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t messagesSearchJson) error {
status, err := cli.Search(&imap2.SearchCriteria{
SeqNum: t.SeqNum,
Uid: t.Uid,
Since: time.Time{},
Before: time.Time{},
SentSince: time.Time{},
SentBefore: time.Time{},
Header: nil,
Body: nil,
Text: nil,
WithFlags: nil,
WithoutFlags: nil,
Larger: 0,
Smaller: 0,
Not: nil,
Or: nil,
})
if err != nil {
return err
}
return json.NewEncoder(rw).Encode(status)
})))
r.POST("/update-messages-flags", auth(imapClient(recv, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t mailboxStatusJson) {
status, err := cli.Status(t.Folder) status, err := cli.Status(t.Folder)
if err != nil { if err != nil {
rw.WriteHeader(http.StatusForbidden) rw.WriteHeader(http.StatusForbidden)
@ -39,7 +127,7 @@ func SetupApiServer(listen string, auth func(callback AuthCallback) httprouter.H
rw.WriteHeader(http.StatusForbidden) rw.WriteHeader(http.StatusForbidden)
return return
} }
err = json.NewEncoder(rw).Encode(json2.ListMessagesJson(messages)) err = json.NewEncoder(rw).Encode(marshal.ListMessagesJson(messages))
if err != nil { if err != nil {
log.Println("list-messages json encode error:", err) log.Println("list-messages json encode error:", err)
} }
@ -96,7 +184,7 @@ func apiError(rw http.ResponseWriter, code int, m string) {
}) })
} }
type IcCallback[T any] func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t T) type IcCallback[T any] func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t T) error
func imapClient[T any](recv Imap, cb IcCallback[T]) AuthCallback { func imapClient[T any](recv Imap, cb IcCallback[T]) AuthCallback {
return func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, b AuthClaims) { return func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, b AuthClaims) {
@ -114,6 +202,9 @@ func imapClient[T any](recv Imap, cb IcCallback[T]) AuthCallback {
rw.WriteHeader(http.StatusInternalServerError) rw.WriteHeader(http.StatusInternalServerError)
return return
} }
cb(rw, req, params, cli, t) err = cb(rw, req, params, cli, t)
if err != nil {
log.Println("[ImapClient] Error:", err)
}
} }
} }

38
imap/marshal/message.go Normal file
View File

@ -0,0 +1,38 @@
package marshal
import (
"encoding/json"
"github.com/emersion/go-imap"
)
var _, _ json.Marshaler = &MessageSliceJson{}, &MessageJson{}
type MessageSliceJson []*imap.Message
func (m MessageSliceJson) MarshalJSON() ([]byte, error) {
a := make([]MessageJson, len(m))
for i := range a {
a[i] = MessageJson(*m[i])
}
return json.Marshal(a)
}
type MessageJson imap.Message
func (m MessageJson) MarshalJSON() ([]byte, error) {
body := make(map[string]imap.Literal, len(m.Body))
for k, v := range m.Body {
body[string(k.FetchItem())] = v
}
return json.Marshal(map[string]any{
"SeqNum": m.SeqNum,
"Items": m.Items,
"Envelope": m.Envelope,
"BodyStructure": m.BodyStructure,
"Flags": m.Flags,
"InternalDate": m.InternalDate,
"Size": m.Size,
"Uid": m.Uid,
"$Body": body,
})
}

7
imap/marshal/seqset.go Normal file
View File

@ -0,0 +1,7 @@
package marshal
import imap2 "github.com/emersion/go-imap"
type SeqSet imap2.SeqSet
var json.mar