Add /ingest endpoint - server-side attachment forwarding to DocSys
This commit is contained in:
parent
ed28bb8a64
commit
59ddae7540
BIN
mail-bridge
BIN
mail-bridge
Binary file not shown.
130
main.go
130
main.go
|
|
@ -496,6 +496,12 @@ func handleMessages(w http.ResponseWriter, r *http.Request, accountName string,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for /messages/{uid}/ingest
|
||||||
|
if len(pathParts) > 1 && pathParts[1] == "ingest" {
|
||||||
|
handleIngestAttachments(w, r, client, folder, uid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case "GET":
|
case "GET":
|
||||||
handleGetMessage(w, r, client, folder, uid)
|
handleGetMessage(w, r, client, folder, uid)
|
||||||
|
|
@ -893,3 +899,127 @@ func handleGetAttachments(w http.ResponseWriter, r *http.Request, client *imapcl
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(attachments)
|
json.NewEncoder(w).Encode(attachments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IngestRequest specifies which attachments to forward to DocSys
|
||||||
|
type IngestRequest struct {
|
||||||
|
Attachments []string `json:"attachments"` // filenames to ingest (empty = all)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IngestResult reports the outcome for each attachment
|
||||||
|
type IngestResult struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Status string `json:"status"` // "success" or "error"
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleIngestAttachments(w http.ResponseWriter, r *http.Request, client *imapclient.Client, folder string, uid uint32) {
|
||||||
|
if r.Method != "POST" {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse request
|
||||||
|
var req IngestRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil && err != io.EOF {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch message with body
|
||||||
|
uidSet := imap.UIDSetNum(imap.UID(uid))
|
||||||
|
options := &imap.FetchOptions{
|
||||||
|
UID: true,
|
||||||
|
Envelope: true,
|
||||||
|
BodySection: []*imap.FetchItemBodySection{{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchCmd := client.Fetch(uidSet, options)
|
||||||
|
msgData := fetchCmd.Next()
|
||||||
|
if msgData == nil {
|
||||||
|
fetchCmd.Close()
|
||||||
|
http.Error(w, "Message not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := msgData.Collect()
|
||||||
|
if err != nil {
|
||||||
|
fetchCmd.Close()
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fetchCmd.Close()
|
||||||
|
|
||||||
|
if len(buf.BodySection) == 0 {
|
||||||
|
http.Error(w, "No message body", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get email metadata for DocSys
|
||||||
|
var from, subject string
|
||||||
|
if env := buf.Envelope; env != nil {
|
||||||
|
subject = env.Subject
|
||||||
|
if len(env.From) > 0 {
|
||||||
|
f := env.From[0]
|
||||||
|
if f.Name != "" {
|
||||||
|
from = fmt.Sprintf("%s <%s@%s>", f.Name, f.Mailbox, f.Host)
|
||||||
|
} else {
|
||||||
|
from = fmt.Sprintf("%s@%s", f.Mailbox, f.Host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract attachments
|
||||||
|
raw := buf.BodySection[0].Bytes
|
||||||
|
attachments := ExtractAttachments(raw)
|
||||||
|
|
||||||
|
if len(attachments) == 0 {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode([]IngestResult{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter to requested attachments (or all if none specified)
|
||||||
|
wantedSet := make(map[string]bool)
|
||||||
|
for _, name := range req.Attachments {
|
||||||
|
wantedSet[name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []IngestResult
|
||||||
|
for _, att := range attachments {
|
||||||
|
// Skip if not in requested list (when list is specified)
|
||||||
|
if len(req.Attachments) > 0 && !wantedSet[att.Filename] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST to DocSys
|
||||||
|
payload := map[string]string{
|
||||||
|
"filename": att.Filename,
|
||||||
|
"content": att.Content,
|
||||||
|
"source": "email",
|
||||||
|
"subject": subject,
|
||||||
|
"from": from,
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadBytes, _ := json.Marshal(payload)
|
||||||
|
resp, err := http.Post("http://localhost:9201/api/ingest", "application/json", bytes.NewReader(payloadBytes))
|
||||||
|
|
||||||
|
result := IngestResult{Filename: att.Filename}
|
||||||
|
if err != nil {
|
||||||
|
result.Status = "error"
|
||||||
|
result.Message = err.Error()
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
result.Status = "error"
|
||||||
|
result.Message = string(body)
|
||||||
|
} else {
|
||||||
|
result.Status = "success"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results = append(results, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(results)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue