package api import ( "net/http" "strings" "time" "archivmail/internal/userstore" "archivmail/pkg/mailparser" ) // handleGetThread returns all mails in a thread, ordered by date ascending. // Access is tenant-isolated identical to handleGetMail. // // GET /api/mail/thread/{threadID} func (s *Server) handleGetThread(w http.ResponseWriter, r *http.Request) { threadID := r.PathValue("threadID") if threadID == "" { writeError(w, http.StatusBadRequest, "missing thread id") return } sess := sessionFromCtx(r.Context()) tenantID := tenantFromCtx(r.Context()) // domain_auditor without tenant → deny if sess.Role == userstore.RoleDomainAuditor && tenantID == nil { writeError(w, http.StatusForbidden, "access denied") return } // For user role, we still filter per mail below ids, err := s.store.GetMailsByThread(r.Context(), threadID, tenantID) if err != nil { writeError(w, http.StatusInternalServerError, "thread lookup failed") return } type mailSummary struct { ID string `json:"id"` From string `json:"from,omitempty"` To string `json:"to,omitempty"` Subject string `json:"subject"` Date string `json:"date,omitempty"` Size int `json:"size"` } mails := make([]mailSummary, 0, len(ids)) for _, id := range ids { raw, err := s.store.Load(id) if err != nil { continue } pm, err := mailparser.Parse(raw) if err != nil { continue } // user isolation if sess.Role == userstore.RoleUser { if sess.Email == "" || !mailBelongsToUser(pm, strings.ToLower(sess.Email)) { continue } } var dateStr string if !pm.Date.IsZero() { dateStr = pm.Date.UTC().Format(time.RFC3339) } mails = append(mails, mailSummary{ ID: id, From: pm.From, To: strings.Join(pm.To, ", "), Subject: pm.Subject, Date: dateStr, Size: len(raw), }) } writeJSON(w, http.StatusOK, map[string]interface{}{ "thread_id": threadID, "total": len(mails), "mails": mails, }) }