Files
phishingclub/backend/controller/email.go
Ronni Skansing efb35f6bcc email attachment endpoint bc
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
2026-01-19 20:59:19 +01:00

406 lines
9.0 KiB
Go

package controller
import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/oapi-codegen/nullable"
"github.com/phishingclub/phishingclub/database"
"github.com/phishingclub/phishingclub/errs"
"github.com/phishingclub/phishingclub/model"
"github.com/phishingclub/phishingclub/repository"
"github.com/phishingclub/phishingclub/service"
"github.com/phishingclub/phishingclub/vo"
)
// EmailOrderByMap is a map between the frontend and the backend
// so the frontend has user friendly names instead of direct references
// to the database schema
// this is tied to a slice in the repository package
var EmailOrderByMap = map[string]string{
"created_at": repository.TableColumn(database.EMAIL_TABLE, "created_at"),
"updated_at": repository.TableColumn(database.EMAIL_TABLE, "created_at"),
"name": repository.TableColumn(database.EMAIL_TABLE, "name"),
"mail_from": repository.TableColumn(database.EMAIL_TABLE, "mail_from"),
"from": repository.TableColumn(database.EMAIL_TABLE, "from"),
"subject": repository.TableColumn(database.EMAIL_TABLE, "subject"),
"tracking_pixel": repository.TableColumn(database.EMAIL_TABLE, "add_tracking_pixel"),
}
// AddAttachmentsToEmailRequest is a request to add attachments to a message
// supports both old format (ids array) and new format (attachments array with isInline)
type AddAttachmentsToEmailRequest struct {
IDs []string `json:"ids"` // deprecated: old format for backward compatibility
Attachments []AttachmentWithInline `json:"attachments"` // new format with isInline support
}
// AttachmentWithInline represents an attachment ID with inline flag
type AttachmentWithInline struct {
ID string `json:"id"`
IsInline bool `json:"isInline"`
}
// RemoveAttachmentFromEmailRequest is a request to remove an attachment from a message
type RemoveAttachmentFromEmailRequest struct {
AttachmentID string `json:"attachmentID"`
}
// SendTestEmailRequest is a request for sending a test of an e-mail
type SendTestEmailRequest struct {
SMTPID *uuid.UUID
DomainID *uuid.UUID
RecipientID *uuid.UUID
}
// Email is a Email controller
type Email struct {
Common
EmailService *service.Email
TemplateService *service.Template
EmailRepository *repository.Email
}
// AddAttachments adds attachments to a email
func (m *Email) AddAttachments(g *gin.Context) {
session, _, ok := m.handleSession(g)
if !ok {
return
}
// parse request
var request AddAttachmentsToEmailRequest
if ok := m.handleParseRequest(g, &request); !ok {
return
}
id, ok := m.handleParseIDParam(g)
if !ok {
return
}
// handle backward compatibility: if old format (ids) is used, convert to new format
if len(request.IDs) > 0 && len(request.Attachments) == 0 {
for _, idStr := range request.IDs {
request.Attachments = append(request.Attachments, AttachmentWithInline{
ID: idStr,
IsInline: false, // default to false for backward compatibility
})
}
}
if len(request.Attachments) == 0 {
m.Response.BadRequestMessage(g, "No attachments provided")
return
}
attachments := []service.AttachmentWithInline{}
for _, att := range request.Attachments {
id, err := uuid.Parse(att.ID)
if err != nil {
m.Logger.Debugw(errs.MsgFailedToParseUUID,
"error", err,
)
m.Response.BadRequestMessage(g, "Invalid attachment ID")
return
}
attachments = append(attachments, service.AttachmentWithInline{
ID: &id,
IsInline: att.IsInline,
})
}
// add attachments to email
err := m.EmailService.AddAttachments(
g.Request.Context(),
session,
id,
attachments,
)
// handle responses
if ok := m.handleErrors(g, err); !ok {
return
}
m.Response.OK(g, gin.H{})
}
// RemoveAttachment removes an attachment from a email
func (m *Email) RemoveAttachment(g *gin.Context) {
// handle session
session, _, ok := m.handleSession(g)
if !ok {
return
}
// parse req
var req RemoveAttachmentFromEmailRequest
if ok := m.handleParseRequest(g, &req); !ok {
return
}
attachmentID, err := uuid.Parse(req.AttachmentID)
if err != nil {
m.Logger.Debugw(errs.MsgFailedToParseUUID,
"error", err,
)
m.Response.BadRequestMessage(g, "Invalid attachment ID")
return
}
emailID, err := uuid.Parse(g.Param("id"))
if err != nil {
m.Logger.Debugw(errs.MsgFailedToParseUUID, "error", err)
m.Response.BadRequestMessage(g, "Invalid message ID")
return
}
// remove attachment from email
err = m.EmailService.RemoveAttachment(
g.Request.Context(),
session,
&emailID,
&attachmentID,
)
// handle responses
if ok := m.handleErrors(g, err); !ok {
return
}
m.Response.OK(g, gin.H{})
}
// Create creates a email
func (m *Email) Create(g *gin.Context) {
session, _, ok := m.handleSession(g)
if !ok {
return
}
// parse req
var req model.Email
if ok := m.handleParseRequest(g, &req); !ok {
return
}
// save email
id, err := m.EmailService.Create(
g,
session,
&req,
)
// handle responses
if ok := m.handleErrors(g, err); !ok {
return
}
m.Response.OK(g, gin.H{
"id": id,
})
}
// SendTestEmail
func (m *Email) SendTestEmail(g *gin.Context) {
session, _, ok := m.handleSession(g)
if !ok {
return
}
// parse request
id, ok := m.handleParseIDParam(g)
if !ok {
return
}
var req SendTestEmailRequest
if ok := m.handleParseRequest(g, &req); !ok {
return
}
companyID := companyIDFromRequestQuery(g)
// send test email
err := m.EmailService.SendTestEmail(
g,
session,
id,
req.SMTPID,
req.DomainID,
req.RecipientID,
companyID,
)
// handle responses
if ok := m.handleErrors(g, err); !ok {
return
}
m.Response.OK(g, gin.H{})
}
// GetByID gets a email by ID
func (m *Email) GetByID(g *gin.Context) {
session, _, ok := m.handleSession(g)
if !ok {
return
}
// parse request
id, ok := m.handleParseIDParam(g)
if !ok {
return
}
companyID := companyIDFromRequestQuery(g)
// get email
email, err := m.EmailService.GetByID(
g.Request.Context(),
session,
id,
companyID,
)
// handle responses
if ok := m.handleErrors(g, err); !ok {
return
}
m.Response.OK(g, email)
}
// GetContentByID gets a email content by ID
func (m *Email) GetContentByID(g *gin.Context) {
session, _, ok := m.handleSession(g)
if !ok {
return
}
// parse request
id, ok := m.handleParseIDParam(g)
if !ok {
return
}
companyID := companyIDFromRequestQuery(g)
// get
email, err := m.EmailService.GetByID(
g.Request.Context(),
session,
id,
companyID,
)
if ok := m.handleErrors(g, err); !ok {
return
}
// build email
domain := &model.Domain{
Name: nullable.NewNullableWithValue(
*vo.NewString255Must("example.test"),
),
}
recipient := model.NewRecipientExample()
campaignRecipient := model.CampaignRecipient{
ID: nullable.NewNullableWithValue(
uuid.New(),
),
Recipient: recipient,
}
apiSender := model.NewAPISenderExample()
emailBody, err := m.TemplateService.CreateMailBody(
g.Request.Context(),
"id",
"/foo",
domain,
&campaignRecipient,
email,
apiSender,
companyID, // use the company context from the email
)
if ok := m.handleErrors(g, err); !ok {
return
}
m.Response.OK(g, emailBody)
}
// GetAll gets all emails using pagination
func (m *Email) GetAll(g *gin.Context) {
// handle session
session, _, ok := m.handleSession(g)
if !ok {
return
}
companyID := companyIDFromRequestQuery(g)
queryArgs, ok := m.handleQueryArgs(g)
if !ok {
return
}
queryArgs.DefaultSortByName()
queryArgs.RemapOrderBy(EmailOrderByMap)
emails, err := m.EmailService.GetAll(
g.Request.Context(),
session,
companyID,
queryArgs,
)
// handle responses
if ok := m.handleErrors(g, err); !ok {
return
}
m.Response.OK(g, emails)
}
// GetOverviews gets all email overviews using pagination
func (m *Email) GetOverviews(g *gin.Context) {
// handle session
session, _, ok := m.handleSession(g)
if !ok {
return
}
companyID := companyIDFromRequestQuery(g)
queryArgs, ok := m.handleQueryArgs(g)
if !ok {
return
}
queryArgs.RemapOrderBy(EmailOrderByMap)
queryArgs.DefaultSortByName()
emails, err := m.EmailService.GetOverviews(
g.Request.Context(),
session,
companyID,
queryArgs,
)
// handle responses
if ok := m.handleErrors(g, err); !ok {
return
}
m.Response.OK(g, emails)
}
// UpdateByID updates a message by ID
func (m *Email) UpdateByID(g *gin.Context) {
session, _, ok := m.handleSession(g)
if !ok {
return
}
// parse request
id, ok := m.handleParseIDParam(g)
if !ok {
return
}
var email model.Email
if ok := m.handleParseRequest(g, &email); !ok {
return
}
// update message
err := m.EmailService.UpdateByID(
g.Request.Context(),
session,
id,
&email,
)
// handle response
if ok := m.handleErrors(g, err); !ok {
return
}
m.Response.OK(g, gin.H{})
}
// DeleteByID deletes a message by ID
func (m *Email) DeleteByID(g *gin.Context) {
session, _, ok := m.handleSession(g)
if !ok {
return
}
// parse request
id, ok := m.handleParseIDParam(g)
if !ok {
return
}
// delete message
err := m.EmailService.DeleteByID(
g.Request.Context(),
session,
id,
)
// handle response
if ok := m.handleErrors(g, err); !ok {
return
}
m.Response.OK(g, gin.H{})
}