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
|
||||
}
|
||||
|
||||
// Check for /messages/{uid}/ingest
|
||||
if len(pathParts) > 1 && pathParts[1] == "ingest" {
|
||||
handleIngestAttachments(w, r, client, folder, uid)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
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")
|
||||
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