2023-08-13 03:04:16 +01:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2023-08-21 00:26:22 +01:00
|
|
|
"github.com/1f349/lotus/imap"
|
2023-09-11 14:55:12 +01:00
|
|
|
"github.com/1f349/lotus/imap/marshal"
|
|
|
|
imap2 "github.com/emersion/go-imap"
|
2023-08-13 03:04:16 +01:00
|
|
|
"github.com/julienschmidt/httprouter"
|
2023-09-11 11:55:52 +01:00
|
|
|
"log"
|
2023-08-13 03:04:16 +01:00
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2023-08-23 16:17:01 +01:00
|
|
|
func SetupApiServer(listen string, auth func(callback AuthCallback) httprouter.Handle, send Smtp, recv Imap) *http.Server {
|
2023-08-13 03:04:16 +01:00
|
|
|
r := httprouter.New()
|
|
|
|
|
2023-08-21 00:26:22 +01:00
|
|
|
// === ACCOUNT ===
|
2023-09-11 14:55:12 +01:00
|
|
|
r.GET("/identities", auth(func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, b AuthClaims) {
|
2023-08-21 00:26:22 +01:00
|
|
|
// TODO(melon): find users aliases and other account data
|
|
|
|
}))
|
|
|
|
|
|
|
|
// === SMTP ===
|
2023-08-23 16:17:01 +01:00
|
|
|
r.POST("/message", auth(MessageSender(send)))
|
2023-08-21 00:26:22 +01:00
|
|
|
|
2023-09-11 14:55:12 +01:00
|
|
|
r.Handle(http.MethodConnect, "/", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
|
|
|
|
|
|
|
})
|
|
|
|
|
2023-08-21 00:26:22 +01:00
|
|
|
// === IMAP ===
|
2023-09-11 14:55:12 +01:00
|
|
|
type mailboxStatusJson struct {
|
2023-08-21 00:26:22 +01:00
|
|
|
Folder string `json:"folder"`
|
|
|
|
}
|
2023-09-11 14:55:12 +01:00
|
|
|
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) {
|
2023-08-21 00:26:22 +01:00
|
|
|
status, err := cli.Status(t.Folder)
|
|
|
|
if err != nil {
|
|
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_ = json.NewEncoder(rw).Encode(status)
|
|
|
|
})))
|
|
|
|
r.GET("/list-messages", auth(imapClient(recv, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t statusJson) {
|
|
|
|
messages, err := cli.Fetch(t.Folder, 1, 100, 100)
|
|
|
|
if err != nil {
|
|
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
2023-09-11 14:55:12 +01:00
|
|
|
err = json.NewEncoder(rw).Encode(marshal.ListMessagesJson(messages))
|
2023-09-11 11:55:52 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Println("list-messages json encode error:", err)
|
|
|
|
}
|
2023-08-21 00:26:22 +01:00
|
|
|
})))
|
|
|
|
r.GET("/search-messages", auth(imapClient(recv, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t statusJson) {
|
|
|
|
status, err := cli.Status(t.Folder)
|
|
|
|
if err != nil {
|
|
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_ = json.NewEncoder(rw).Encode(status)
|
|
|
|
})))
|
|
|
|
r.POST("/create-message", auth(imapClient(recv, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t statusJson) {
|
|
|
|
status, err := cli.Status(t.Folder)
|
|
|
|
if err != nil {
|
|
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
|
|
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 statusJson) {
|
|
|
|
status, err := cli.Status(t.Folder)
|
|
|
|
if err != nil {
|
|
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_ = json.NewEncoder(rw).Encode(status)
|
|
|
|
})))
|
|
|
|
r.POST("/copy-messages", auth(imapClient(recv, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t statusJson) {
|
|
|
|
status, err := cli.Status(t.Folder)
|
|
|
|
if err != nil {
|
|
|
|
rw.WriteHeader(http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_ = json.NewEncoder(rw).Encode(status)
|
|
|
|
})))
|
2023-08-13 03:04:16 +01:00
|
|
|
|
|
|
|
return &http.Server{
|
2023-08-21 00:26:22 +01:00
|
|
|
Addr: listen,
|
2023-08-13 03:04:16 +01:00
|
|
|
Handler: r,
|
|
|
|
ReadTimeout: time.Minute,
|
|
|
|
ReadHeaderTimeout: time.Minute,
|
|
|
|
WriteTimeout: time.Minute,
|
|
|
|
IdleTimeout: time.Minute,
|
|
|
|
MaxHeaderBytes: 2500,
|
|
|
|
}
|
|
|
|
}
|
2023-08-21 00:26:22 +01:00
|
|
|
|
|
|
|
// apiError outputs a generic JSON error message
|
|
|
|
func apiError(rw http.ResponseWriter, code int, m string) {
|
|
|
|
rw.WriteHeader(code)
|
|
|
|
_ = json.NewEncoder(rw).Encode(map[string]string{
|
|
|
|
"error": m,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-09-11 14:55:12 +01:00
|
|
|
type IcCallback[T any] func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, cli *imap.Client, t T) error
|
2023-08-21 00:26:22 +01:00
|
|
|
|
2023-08-23 16:17:01 +01:00
|
|
|
func imapClient[T any](recv Imap, cb IcCallback[T]) AuthCallback {
|
2023-08-21 00:26:22 +01:00
|
|
|
return func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, b AuthClaims) {
|
|
|
|
if req.Body == nil {
|
|
|
|
rw.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var t T
|
|
|
|
if json.NewDecoder(req.Body).Decode(&t) != nil {
|
|
|
|
rw.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
cli, err := recv.MakeClient(b.Subject)
|
|
|
|
if err != nil {
|
|
|
|
rw.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
2023-09-11 14:55:12 +01:00
|
|
|
err = cb(rw, req, params, cli, t)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("[ImapClient] Error:", err)
|
|
|
|
}
|
2023-08-21 00:26:22 +01:00
|
|
|
}
|
|
|
|
}
|