GetOutboundMessageForUser returns a full message row (including body, HITL fields, and attachments) if it exists and is owned by userID (via the agent the row belongs to). Inbound messages and cross-user access both return ErrMessageNotFound — the caller should not be able to distinguish "does not e
(ctx context.Context, messageID, userID string)
| 1571 | // return ErrMessageNotFound — the caller should not be able to distinguish |
| 1572 | // "does not exist" from "belongs to someone else". |
| 1573 | func (s *Store) GetOutboundMessageForUser(ctx context.Context, messageID, userID string) (*Message, error) { |
| 1574 | m := &Message{} |
| 1575 | var ( |
| 1576 | bodyText, bodyHTML *string |
| 1577 | attachments []byte |
| 1578 | method, msgType *string |
| 1579 | approvalExpires *time.Time |
| 1580 | reviewedAt *time.Time |
| 1581 | rejectionReason *string |
| 1582 | reviewedByID *string |
| 1583 | reviewedByName *string |
| 1584 | ) |
| 1585 | err := s.pool.QueryRow(ctx, |
| 1586 | `SELECT m.id, m.agent_id, m.direction, m.sender, m.recipient, m.subject, |
| 1587 | m.email_message_id, COALESCE(m.provider_message_id, ''), |
| 1588 | m.method, m.message_type, |
| 1589 | m.conversation_id, m.created_at, m.expires_at, |
| 1590 | m.to_recipients, m.cc, m.bcc, |
| 1591 | m.status, m.approval_expires_at, m.reviewed_at, |
| 1592 | m.rejection_reason, m.edited, |
| 1593 | m.body_text, m.body_html, m.attachments_json, |
| 1594 | m.reviewed_by_user_id, r.name, |
| 1595 | COALESCE(m.delivery_status, ''), COALESCE(m.delivery_detail, ''), COALESCE(m.sent_as, '') |
| 1596 | FROM messages m |
| 1597 | JOIN agent_identities a ON a.id = m.agent_id |
| 1598 | LEFT JOIN users r ON r.id = m.reviewed_by_user_id |
| 1599 | WHERE m.id = $1 AND a.user_id = $2 AND m.direction = 'outbound'`, |
| 1600 | messageID, userID, |
| 1601 | ).Scan( |
| 1602 | &m.ID, &m.AgentID, &m.Direction, &m.Sender, &m.Recipient, &m.Subject, |
| 1603 | &m.EmailMessageID, &m.ProviderMessageID, |
| 1604 | &method, &msgType, |
| 1605 | &m.ConversationID, &m.CreatedAt, &m.ExpiresAt, |
| 1606 | &m.ToRecipients, &m.CC, &m.BCC, |
| 1607 | &m.Status, &approvalExpires, &reviewedAt, |
| 1608 | &rejectionReason, &m.Edited, |
| 1609 | &bodyText, &bodyHTML, &attachments, |
| 1610 | &reviewedByID, &reviewedByName, |
| 1611 | &m.DeliveryStatus, &m.DeliveryDetail, &m.SentAs, |
| 1612 | ) |
| 1613 | if err != nil { |
| 1614 | return nil, ErrMessageNotFound |
| 1615 | } |
| 1616 | if method != nil { |
| 1617 | m.Method = *method |
| 1618 | } |
| 1619 | if msgType != nil { |
| 1620 | m.Type = *msgType |
| 1621 | } |
| 1622 | if approvalExpires != nil { |
| 1623 | m.ApprovalExpiresAt = approvalExpires |
| 1624 | } |
| 1625 | if reviewedAt != nil { |
| 1626 | m.ReviewedAt = reviewedAt |
| 1627 | } |
| 1628 | if rejectionReason != nil { |
| 1629 | m.RejectionReason = *rejectionReason |
| 1630 | } |