chore: import clean Agent Desk snapshot (2/7)
This commit is contained in:
115
internal/handlers/third/email_handler.go
Normal file
115
internal/handlers/third/email_handler.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package third
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"agent-desk/internal/pkg/dto/request"
|
||||
"agent-desk/internal/pkg/httpx"
|
||||
"agent-desk/internal/pkg/httpx/params"
|
||||
"agent-desk/internal/services"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// EmailPostWebhook receives incoming inbound email webhook events from Cloudflare, Brevo, SendGrid, Postmark, Mailgun, or SMTP forwarders.
|
||||
func EmailPostWebhook(ctx *gin.Context) {
|
||||
channelID := strings.TrimSpace(ctx.Param("channel_id"))
|
||||
if channelID == "" {
|
||||
channelID = strings.TrimSpace(ctx.Query("channel_id"))
|
||||
}
|
||||
|
||||
secretHeader := ctx.GetHeader("X-Webhook-Secret")
|
||||
if secretHeader == "" {
|
||||
secretHeader = ctx.GetHeader("X-Brevo-Webhook-Secret")
|
||||
}
|
||||
if secretHeader == "" {
|
||||
secretHeader = ctx.GetHeader("X-Postmark-Webhook-Secret")
|
||||
}
|
||||
if secretHeader == "" {
|
||||
secretHeader = ctx.Query("secret")
|
||||
}
|
||||
|
||||
contentType := ctx.GetHeader("Content-Type")
|
||||
|
||||
var formValues url.Values
|
||||
var bodyBytes []byte
|
||||
|
||||
if strings.Contains(strings.ToLower(contentType), "multipart/form-data") {
|
||||
if err := ctx.Request.ParseMultipartForm(32 << 20); err == nil && ctx.Request.MultipartForm != nil {
|
||||
formValues = ctx.Request.MultipartForm.Value
|
||||
}
|
||||
} else if strings.Contains(strings.ToLower(contentType), "application/x-www-form-urlencoded") {
|
||||
if err := ctx.Request.ParseForm(); err == nil {
|
||||
formValues = ctx.Request.PostForm
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Request.Body != nil {
|
||||
bodyBytes, _ = io.ReadAll(ctx.Request.Body)
|
||||
ctx.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
}
|
||||
|
||||
if err := services.EmailInboundService.HandleWebhook(ctx.Request.Context(), channelID, secretHeader, contentType, bodyBytes, formValues); err != nil {
|
||||
ctx.JSON(http.StatusOK, gin.H{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, gin.H{"ok": true, "message": "email processed"})
|
||||
}
|
||||
|
||||
// EmailPostRequest opens a support request from an external system and continues
|
||||
// the conversation over email.
|
||||
//
|
||||
// It is the API counterpart to EmailPostWebhook: same channel, same identity, but
|
||||
// the caller supplies the fields instead of a parsed email, so an integration
|
||||
// does not have to run a mail forwarder to reach support.
|
||||
//
|
||||
// Presenting X-Webhook-Secret marks the submitted contact details as verified.
|
||||
// Omitting it files the request as self-reported, which agents can see. A wrong
|
||||
// secret is rejected rather than treated as a guest submission.
|
||||
func EmailPostRequest(ctx *gin.Context) {
|
||||
channelID := strings.TrimSpace(ctx.Param("channel_id"))
|
||||
if channelID == "" {
|
||||
channelID = strings.TrimSpace(ctx.Query("channel_id"))
|
||||
}
|
||||
|
||||
req := request.EmailRequestRequest{}
|
||||
if err := params.ReadJSON(ctx, &req); err != nil {
|
||||
httpx.WriteJSON(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := services.EmailRequestService.HandleRequest(
|
||||
ctx.Request.Context(),
|
||||
channelID,
|
||||
webhookSecretFromHeader(ctx),
|
||||
services.EmailRequestInput{
|
||||
Email: req.Email,
|
||||
Name: req.Name,
|
||||
Subject: req.Subject,
|
||||
Message: req.Message,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
httpx.WriteJSON(ctx, err)
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(ctx, result)
|
||||
}
|
||||
|
||||
// webhookSecretFromHeader reads the channel secret. The query-string form the
|
||||
// webhook accepts is deliberately not honoured here: this endpoint is meant to be
|
||||
// called by programs, and a secret in a URL leaks into access logs and proxy
|
||||
// history for no benefit.
|
||||
func webhookSecretFromHeader(ctx *gin.Context) string {
|
||||
for _, header := range []string{"X-Webhook-Secret", "X-Breveo-Webhook-Secret", "X-Postmark-Webhook-Secret"} {
|
||||
if value := strings.TrimSpace(ctx.GetHeader(header)); value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user