diff options
| author | Bobby <[email protected]> | 2026-03-08 08:11:41 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2026-03-08 08:11:41 +0530 |
| commit | b2a231280ce3265d20cdc5f317ae1bcc5eb59924 (patch) | |
| tree | 90eb1a5f5409025db47097e2e083361f8fa52555 /services | |
| parent | 662dd2069dc8590e8b54823a33726464cf10c4e7 (diff) | |
| download | dove-b2a231280ce3265d20cdc5f317ae1bcc5eb59924.tar.xz dove-b2a231280ce3265d20cdc5f317ae1bcc5eb59924.zip | |
Add webmail email management templates and storage utilities
- Implemented email listing template with read/unread and star functionality.
- Created empty state template for webmail when no emails are present.
- Developed folder navigation template for managing email folders.
- Added email preview template for displaying selected email details.
- Introduced storage utilities for managing email files, including creation, reading, moving, and deletion.
- Defined constants for storage paths and error messages related to file operations.
Diffstat (limited to 'services')
| -rw-r--r-- | services/mail/mailboxes.go | 8 | ||||
| -rw-r--r-- | services/mail/messages.go | 25 | ||||
| -rw-r--r-- | services/mail/webmail.go | 700 |
3 files changed, 729 insertions, 4 deletions
diff --git a/services/mail/mailboxes.go b/services/mail/mailboxes.go index 811e6bc..d940907 100644 --- a/services/mail/mailboxes.go +++ b/services/mail/mailboxes.go @@ -34,10 +34,6 @@ type EditMailboxFormResponse struct { Domains []domainModel.Domain `json:"domains"` } -type MailboxView struct { - Address string -} - func ListMailboxes(pagination meta.Pagination, sorting meta.Sorting, search string) meta.PaginatedResponse { mailboxes, total := mailRepo.ListMailboxes(pagination, sorting, search) return pagination.Response(mailboxes, total) @@ -89,6 +85,10 @@ func CreateMailbox(request CreateMailboxRequest) *shortcuts.Error { return shortcuts.ServiceError(shortcuts.Internal, CreationFailed) } + if seedError := mailRepo.SeedFoldersForMailbox(mailbox.ID); seedError != nil { + return shortcuts.ServiceError(shortcuts.Internal, FolderSeedFailed) + } + return nil } diff --git a/services/mail/messages.go b/services/mail/messages.go index 118b630..530ea45 100644 --- a/services/mail/messages.go +++ b/services/mail/messages.go @@ -20,6 +20,31 @@ const ( UserNotFound = "The selected user does not exist." UserRequired = "A user must be selected for the mailbox." + EmailNotFound = "Email not found." + EmailMoveFailed = "Failed to move email." + EmailDeleteFailed = "Failed to delete email." + EmailSendFailed = "Failed to send email." + EmailDraftFailed = "Failed to save draft." + EmailUpdateFailed = "Failed to update email." + EmailStorageFailed = "Failed to store email file." + EmailFileReadFailed = "Failed to read email content." + + LogPrefix = "mail" + + FolderCreationFailed = "Failed to create folder." + FolderDeletionFailed = "Failed to delete folder." + FolderNameRequired = "Folder name is required." + FolderNotFound = "Folder not found." + FolderSeedFailed = "Failed to create default folders." + FolderIsSystem = "System folders cannot be deleted." + FolderHasEmails = "Cannot delete a folder that contains emails. Move or delete the emails first." + + NoMailboxesExist = "No mailboxes exist. Create a mailbox first." + RecipientRequired = "A recipient address is required." + RecipientNotFound = "The recipient mailbox does not exist." + SenderRequired = "A sender mailbox must be selected." + SubjectRequired = "Subject is required." + DisplayNameRequired = "Display name is required." UserAlreadyExists = "A user with this username already exists." UserCreationFailed = "Failed to create user." diff --git a/services/mail/webmail.go b/services/mail/webmail.go new file mode 100644 index 0000000..5dbef75 --- /dev/null +++ b/services/mail/webmail.go @@ -0,0 +1,700 @@ +package mail + +import ( + "fmt" + "regexp" + "strings" + "time" + + mailModel "dove/models/mail" + mailRepo "dove/repositories/mail" + "dove/utils/logger" + "dove/utils/meta" + "dove/utils/shortcuts" + "dove/utils/storage" +) + +type FolderWithCount struct { + mailModel.Folder + UnreadCount int64 `json:"unread_count"` +} + +type WebMailResponse struct { + Mailboxes []mailModel.Mailbox `json:"mailboxes"` + ActiveMailbox mailModel.Mailbox `json:"active_mailbox"` + Folders []FolderWithCount `json:"folders"` + ActiveFolder FolderWithCount `json:"active_folder"` + Emails []mailModel.Email `json:"emails"` + TotalEmails int64 `json:"total_emails"` + ActiveEmail *mailModel.Email `json:"active_email"` + IsStarredView bool `json:"is_starred_view"` + StarredCount int64 `json:"starred_count"` + Search string `json:"search"` +} + +type EmailListResponse struct { + Emails []mailModel.Email `json:"emails"` + TotalEmails int64 `json:"total_emails"` + Search string `json:"search"` +} + +type EmailPreviewResponse struct { + Email mailModel.Email `json:"email"` + EmailBody string `json:"email_body"` + ActiveMailbox mailModel.Mailbox `json:"active_mailbox"` + Folders []mailModel.Folder `json:"folders"` +} + +type ComposeAddress struct { + Address string `json:"address"` + DisplayName string `json:"display_name"` + MailboxID uint `json:"mailbox_id"` +} + +type ComposeResponse struct { + Mailboxes []mailModel.Mailbox `json:"mailboxes"` + ActiveMailbox mailModel.Mailbox `json:"active_mailbox"` + FromAddresses []ComposeAddress `json:"from_addresses"` + AllRecipients []ComposeAddress `json:"all_recipients"` + DraftID uint `json:"draft_id"` + ReplyTo *mailModel.Email `json:"reply_to"` +} + +type SendEmailRequest struct { + FromMailboxID uint `form:"from_mailbox_id"` + ToAddress string `form:"to_address"` + CcAddresses string `form:"cc_addresses"` + BccAddresses string `form:"bcc_addresses"` + Subject string `form:"subject"` + Body string `form:"body"` + DraftID uint `form:"draft_id"` +} + +type MoveEmailRequest struct { + FolderID uint `form:"folder_id"` +} + +type BulkActionRequest struct { + EmailIDs []uint `form:"email_ids"` + Action string `form:"action"` + FolderID uint `form:"folder_id"` +} + +type CreateFolderRequest struct { + Name string `form:"name"` + MailboxID uint +} + +func WebMailData(mailboxID uint, folderSlug string, search string, pagination meta.Pagination, sorting meta.Sorting) (*WebMailResponse, *shortcuts.Error) { + mailboxes := mailRepo.AllMailboxes() + if len(mailboxes) == 0 { + return nil, shortcuts.ServiceError(shortcuts.NotFound, NoMailboxesExist) + } + + var activeMailbox *mailModel.Mailbox + if mailboxID > 0 { + for index := range mailboxes { + if mailboxes[index].ID == mailboxID { + activeMailbox = &mailboxes[index] + break + } + } + } + + if activeMailbox == nil { + activeMailbox = &mailboxes[0] + } + + folders := mailRepo.FindFoldersByMailboxID(activeMailbox.ID) + foldersWithCounts := buildFolderCounts(folders) + + isStarredView := folderSlug == "starred" + + var activeFolder *FolderWithCount + if !isStarredView { + for index := range foldersWithCounts { + if foldersWithCounts[index].Slug == folderSlug { + activeFolder = &foldersWithCounts[index] + break + } + } + + if activeFolder == nil && len(foldersWithCounts) > 0 { + activeFolder = &foldersWithCounts[0] + } + } + + var emails []mailModel.Email + var totalEmails int64 + + if isStarredView { + emails, totalEmails = mailRepo.ListStarredEmails(activeMailbox.ID, pagination, sorting, search) + } else if activeFolder != nil { + emails, totalEmails = mailRepo.ListEmailsByFolder(activeFolder.ID, pagination, sorting, search) + } + + starredCount := mailRepo.CountStarredByMailboxID(activeMailbox.ID) + + response := &WebMailResponse{ + Mailboxes: mailboxes, + ActiveMailbox: *activeMailbox, + Folders: foldersWithCounts, + Emails: emails, + TotalEmails: totalEmails, + IsStarredView: isStarredView, + StarredCount: starredCount, + Search: search, + } + + if activeFolder != nil { + response.ActiveFolder = *activeFolder + } + + return response, nil +} + +func EmailListData(folderID uint, search string, pagination meta.Pagination, sorting meta.Sorting) EmailListResponse { + emails, total := mailRepo.ListEmailsByFolder(folderID, pagination, sorting, search) + return EmailListResponse{ + Emails: emails, + TotalEmails: total, + Search: search, + } +} + +func StarredEmailListData(mailboxID uint, search string, pagination meta.Pagination, sorting meta.Sorting) EmailListResponse { + emails, total := mailRepo.ListStarredEmails(mailboxID, pagination, sorting, search) + return EmailListResponse{ + Emails: emails, + TotalEmails: total, + Search: search, + } +} + +type FolderSidebarResponse struct { + Folders []FolderWithCount `json:"folders"` + ActiveFolder FolderWithCount `json:"active_folder"` + ActiveMailbox mailModel.Mailbox `json:"active_mailbox"` + IsStarredView bool `json:"is_starred_view"` + StarredCount int64 `json:"starred_count"` +} + +func FolderSidebarData(mailboxID uint, folderSlug string) (*FolderSidebarResponse, *shortcuts.Error) { + mailbox := mailRepo.FindMailboxByIDWithRelations(mailboxID) + if mailbox == nil { + return nil, shortcuts.ServiceError(shortcuts.NotFound, MailboxNotFound) + } + + folders := mailRepo.FindFoldersByMailboxID(mailboxID) + foldersWithCounts := buildFolderCounts(folders) + + isStarredView := folderSlug == "starred" + starredCount := mailRepo.CountStarredByMailboxID(mailboxID) + + response := &FolderSidebarResponse{ + Folders: foldersWithCounts, + ActiveMailbox: *mailbox, + IsStarredView: isStarredView, + StarredCount: starredCount, + } + + if !isStarredView { + for index := range foldersWithCounts { + if foldersWithCounts[index].Slug == folderSlug { + response.ActiveFolder = foldersWithCounts[index] + break + } + } + } + + return response, nil +} + +func IsEmailUnread(emailID uint) bool { + email := mailRepo.FindEmailByID(emailID) + return email != nil && !email.IsRead +} + +func EmailPreviewData(emailID uint, mailboxID uint) (*EmailPreviewResponse, *shortcuts.Error) { + email := mailRepo.FindEmailByIDWithRelations(emailID) + if email == nil { + return nil, shortcuts.ServiceError(shortcuts.NotFound, EmailNotFound) + } + + if !email.IsRead { + mailRepo.MarkEmailAsRead(emailID) + email.IsRead = true + } + + mailbox := mailRepo.FindMailboxByIDWithRelations(mailboxID) + if mailbox == nil { + return nil, shortcuts.ServiceError(shortcuts.NotFound, MailboxNotFound) + } + + folders := mailRepo.FindFoldersByMailboxID(mailboxID) + + emailBody := readEmailBody(email.Mailbox.Address, email.Folder.Slug, email.Filename) + + return &EmailPreviewResponse{ + Email: *email, + EmailBody: emailBody, + ActiveMailbox: *mailbox, + Folders: folders, + }, nil +} + +func ComposeData(mailboxID uint, replyToEmailID uint) (*ComposeResponse, *shortcuts.Error) { + mailboxes := mailRepo.AllMailboxes() + if len(mailboxes) == 0 { + return nil, shortcuts.ServiceError(shortcuts.NotFound, NoMailboxesExist) + } + + var activeMailbox *mailModel.Mailbox + for index := range mailboxes { + if mailboxes[index].ID == mailboxID { + activeMailbox = &mailboxes[index] + break + } + } + + if activeMailbox == nil { + activeMailbox = &mailboxes[0] + } + + fromAddresses := buildFromAddresses(activeMailbox, mailboxes) + allRecipients := buildAllRecipients(mailboxes) + + response := &ComposeResponse{ + Mailboxes: mailboxes, + ActiveMailbox: *activeMailbox, + FromAddresses: fromAddresses, + AllRecipients: allRecipients, + } + + if replyToEmailID > 0 { + replyEmail := mailRepo.FindEmailByID(replyToEmailID) + if replyEmail != nil { + response.ReplyTo = replyEmail + } + } + + return response, nil +} + +func buildFromAddresses(activeMailbox *mailModel.Mailbox, allMailboxes []mailModel.Mailbox) []ComposeAddress { + var fromAddresses []ComposeAddress + + for _, mailbox := range allMailboxes { + if mailbox.UserID != activeMailbox.UserID { + continue + } + + fromAddresses = append(fromAddresses, ComposeAddress{ + Address: mailbox.Address, + DisplayName: mailbox.User.DisplayName, + MailboxID: mailbox.ID, + }) + + for _, alias := range mailbox.Aliases { + fromAddresses = append(fromAddresses, ComposeAddress{ + Address: alias.SourceAddress, + DisplayName: mailbox.User.DisplayName, + MailboxID: mailbox.ID, + }) + } + } + + return fromAddresses +} + +func buildAllRecipients(allMailboxes []mailModel.Mailbox) []ComposeAddress { + var recipients []ComposeAddress + + for _, mailbox := range allMailboxes { + recipients = append(recipients, ComposeAddress{ + Address: mailbox.Address, + DisplayName: mailbox.User.DisplayName, + MailboxID: mailbox.ID, + }) + + for _, alias := range mailbox.Aliases { + recipients = append(recipients, ComposeAddress{ + Address: alias.SourceAddress, + DisplayName: mailbox.User.DisplayName, + MailboxID: mailbox.ID, + }) + } + } + + return recipients +} + +func SendEmail(request SendEmailRequest) *shortcuts.Error { + toAddress := strings.TrimSpace(request.ToAddress) + subject := strings.TrimSpace(request.Subject) + body := strings.TrimSpace(request.Body) + + switch { + case request.FromMailboxID == 0: + return shortcuts.ServiceError(shortcuts.BadRequest, SenderRequired) + case toAddress == "": + return shortcuts.ServiceError(shortcuts.BadRequest, RecipientRequired) + case subject == "": + return shortcuts.ServiceError(shortcuts.BadRequest, SubjectRequired) + } + + senderMailbox := mailRepo.FindMailboxByIDWithRelations(request.FromMailboxID) + if senderMailbox == nil { + return shortcuts.ServiceError(shortcuts.NotFound, MailboxNotFound) + } + + recipientMailbox := mailRepo.FindMailboxByAddress(toAddress) + + sentFolder := mailRepo.FindFolderBySlug(senderMailbox.ID, "sent") + if sentFolder == nil { + return shortcuts.ServiceError(shortcuts.Internal, FolderNotFound) + } + + messageID := generateMessageID(senderMailbox.Address) + filename := generateFilename() + + sentFilename := filename + "-sent" + sentEmail := &mailModel.Email{ + MailboxID: senderMailbox.ID, + FolderID: sentFolder.ID, + MessageID: messageID, + Filename: sentFilename, + FromAddress: senderMailbox.Address, + FromName: senderMailbox.User.DisplayName, + ToAddresses: toAddress, + CcAddresses: strings.TrimSpace(request.CcAddresses), + Subject: subject, + Snippet: truncateSnippet(body), + Size: int64(len(body)), + IsRead: true, + } + + if createError := mailRepo.CreateEmail(sentEmail); createError != nil { + return shortcuts.ServiceError(shortcuts.Internal, EmailSendFailed) + } + + if writeError := storage.WriteMailFile(senderMailbox.Address, "sent", sentFilename, []byte(body)); writeError != nil { + logger.Warnf(LogPrefix, EmailStorageFailed, writeError) + } + + if recipientMailbox != nil { + inboxFolder := mailRepo.FindFolderBySlug(recipientMailbox.ID, "inbox") + if inboxFolder != nil { + recvFilename := filename + "-recv" + receivedEmail := &mailModel.Email{ + MailboxID: recipientMailbox.ID, + FolderID: inboxFolder.ID, + MessageID: messageID, + Filename: recvFilename, + FromAddress: senderMailbox.Address, + FromName: senderMailbox.User.DisplayName, + ToAddresses: toAddress, + CcAddresses: strings.TrimSpace(request.CcAddresses), + Subject: subject, + Snippet: truncateSnippet(body), + Size: int64(len(body)), + IsRead: false, + } + + if createError := mailRepo.CreateEmail(receivedEmail); createError == nil { + if writeError := storage.WriteMailFile(recipientMailbox.Address, "inbox", recvFilename, []byte(body)); writeError != nil { + logger.Warnf(LogPrefix, EmailStorageFailed, writeError) + } + } + } + } + + if request.DraftID > 0 { + deleteDraftWithFile(request.DraftID, senderMailbox.Address) + } + + return nil +} + +func SaveDraft(request SendEmailRequest) (*mailModel.Email, *shortcuts.Error) { + if request.FromMailboxID == 0 { + return nil, shortcuts.ServiceError(shortcuts.BadRequest, SenderRequired) + } + + senderMailbox := mailRepo.FindMailboxByIDWithRelations(request.FromMailboxID) + if senderMailbox == nil { + return nil, shortcuts.ServiceError(shortcuts.NotFound, MailboxNotFound) + } + + draftsFolder := mailRepo.FindFolderBySlug(senderMailbox.ID, "drafts") + if draftsFolder == nil { + return nil, shortcuts.ServiceError(shortcuts.Internal, FolderNotFound) + } + + body := strings.TrimSpace(request.Body) + + if request.DraftID > 0 { + existingDraft := mailRepo.FindEmailByID(request.DraftID) + if existingDraft != nil { + existingDraft.ToAddresses = strings.TrimSpace(request.ToAddress) + existingDraft.CcAddresses = strings.TrimSpace(request.CcAddresses) + existingDraft.Subject = strings.TrimSpace(request.Subject) + existingDraft.Snippet = truncateSnippet(body) + existingDraft.Size = int64(len(body)) + + if updateError := mailRepo.UpdateEmail(existingDraft); updateError != nil { + return nil, shortcuts.ServiceError(shortcuts.Internal, EmailDraftFailed) + } + + if writeError := storage.WriteMailFile(senderMailbox.Address, "drafts", existingDraft.Filename, []byte(body)); writeError != nil { + logger.Warnf(LogPrefix, EmailStorageFailed, writeError) + } + + return existingDraft, nil + } + } + + draftFilename := generateFilename() + "-draft" + draft := &mailModel.Email{ + MailboxID: senderMailbox.ID, + FolderID: draftsFolder.ID, + MessageID: generateMessageID(senderMailbox.Address), + Filename: draftFilename, + FromAddress: senderMailbox.Address, + FromName: senderMailbox.User.DisplayName, + ToAddresses: strings.TrimSpace(request.ToAddress), + CcAddresses: strings.TrimSpace(request.CcAddresses), + Subject: strings.TrimSpace(request.Subject), + Snippet: truncateSnippet(body), + Size: int64(len(body)), + IsRead: true, + } + + if createError := mailRepo.CreateEmail(draft); createError != nil { + return nil, shortcuts.ServiceError(shortcuts.Internal, EmailDraftFailed) + } + + if writeError := storage.WriteMailFile(senderMailbox.Address, "drafts", draftFilename, []byte(body)); writeError != nil { + logger.Warnf(LogPrefix, EmailStorageFailed, writeError) + } + + return draft, nil +} + +func ToggleStar(emailID uint) *shortcuts.Error { + email := mailRepo.FindEmailByID(emailID) + if email == nil { + return shortcuts.ServiceError(shortcuts.NotFound, EmailNotFound) + } + + email.IsStarred = !email.IsStarred + if updateError := mailRepo.UpdateEmail(email); updateError != nil { + return shortcuts.ServiceError(shortcuts.Internal, EmailUpdateFailed) + } + + return nil +} + +func MarkRead(emailID uint) *shortcuts.Error { + if markError := mailRepo.MarkEmailAsRead(emailID); markError != nil { + return shortcuts.ServiceError(shortcuts.Internal, EmailUpdateFailed) + } + return nil +} + +func MarkUnread(emailID uint) *shortcuts.Error { + if markError := mailRepo.MarkEmailAsUnread(emailID); markError != nil { + return shortcuts.ServiceError(shortcuts.Internal, EmailUpdateFailed) + } + return nil +} + +func MoveEmail(emailID uint, request MoveEmailRequest) *shortcuts.Error { + email := mailRepo.FindEmailByIDWithRelations(emailID) + if email == nil { + return shortcuts.ServiceError(shortcuts.NotFound, EmailNotFound) + } + + targetFolder := mailRepo.FindFolderByID(request.FolderID) + if targetFolder == nil { + return shortcuts.ServiceError(shortcuts.NotFound, FolderNotFound) + } + + sourceFolderSlug := email.Folder.Slug + + if moveError := mailRepo.MoveEmailToFolder(emailID, request.FolderID); moveError != nil { + return shortcuts.ServiceError(shortcuts.Internal, EmailMoveFailed) + } + + if moveFileError := storage.MoveMailFile(email.Mailbox.Address, sourceFolderSlug, targetFolder.Slug, email.Filename); moveFileError != nil { + logger.Warnf(LogPrefix, EmailStorageFailed, moveFileError) + } + + return nil +} + +func TrashEmail(emailID uint, mailboxID uint) *shortcuts.Error { + email := mailRepo.FindEmailByIDWithRelations(emailID) + if email == nil { + return shortcuts.ServiceError(shortcuts.NotFound, EmailNotFound) + } + + trashFolder := mailRepo.FindFolderBySlug(mailboxID, "trash") + if trashFolder == nil { + return shortcuts.ServiceError(shortcuts.Internal, FolderNotFound) + } + + sourceFolderSlug := email.Folder.Slug + + if email.FolderID == trashFolder.ID { + if deleteError := mailRepo.DeleteEmail(email); deleteError != nil { + return shortcuts.ServiceError(shortcuts.Internal, EmailDeleteFailed) + } + storage.DeleteMailFile(email.Mailbox.Address, sourceFolderSlug, email.Filename) + return nil + } + + if moveError := mailRepo.MoveEmailToFolder(emailID, trashFolder.ID); moveError != nil { + return shortcuts.ServiceError(shortcuts.Internal, EmailMoveFailed) + } + + if moveFileError := storage.MoveMailFile(email.Mailbox.Address, sourceFolderSlug, "trash", email.Filename); moveFileError != nil { + logger.Warnf(LogPrefix, EmailStorageFailed, moveFileError) + } + + return nil +} + +func BulkAction(request BulkActionRequest, mailboxID uint) *shortcuts.Error { + if len(request.EmailIDs) == 0 { + return nil + } + + switch request.Action { + case "read": + mailRepo.BulkMarkAsRead(request.EmailIDs) + case "unread": + mailRepo.BulkMarkAsUnread(request.EmailIDs) + case "move": + if request.FolderID == 0 { + return shortcuts.ServiceError(shortcuts.BadRequest, FolderNotFound) + } + mailRepo.BulkMoveEmails(request.EmailIDs, request.FolderID) + case "trash": + trashFolder := mailRepo.FindFolderBySlug(mailboxID, "trash") + if trashFolder == nil { + return shortcuts.ServiceError(shortcuts.Internal, FolderNotFound) + } + mailRepo.BulkMoveEmails(request.EmailIDs, trashFolder.ID) + case "delete": + mailRepo.BulkDeleteEmails(request.EmailIDs) + } + + return nil +} + +func CreateFolder(request CreateFolderRequest) *shortcuts.Error { + folderName := strings.TrimSpace(request.Name) + if folderName == "" { + return shortcuts.ServiceError(shortcuts.BadRequest, FolderNameRequired) + } + + slug := strings.ToLower(strings.ReplaceAll(folderName, " ", "-")) + + existing := mailRepo.FindFolderBySlug(request.MailboxID, slug) + if existing != nil { + return shortcuts.ServiceError(shortcuts.Unprocessable, "A folder with this name already exists.") + } + + nextPosition := mailRepo.MaxFolderPosition(request.MailboxID) + 1 + + folder := &mailModel.Folder{ + Name: folderName, + Slug: slug, + MailboxID: request.MailboxID, + IsSystem: false, + Position: nextPosition, + } + + if createError := mailRepo.CreateFolder(folder); createError != nil { + return shortcuts.ServiceError(shortcuts.Internal, FolderCreationFailed) + } + + return nil +} + +func DeleteFolder(folderID uint) *shortcuts.Error { + folder := mailRepo.FindFolderByID(folderID) + if folder == nil { + return shortcuts.ServiceError(shortcuts.NotFound, FolderNotFound) + } + + if folder.IsSystem { + return shortcuts.ServiceError(shortcuts.Unprocessable, FolderIsSystem) + } + + emailCount := mailRepo.CountEmailsByFolderID(folderID) + if emailCount > 0 { + return shortcuts.ServiceError(shortcuts.Unprocessable, FolderHasEmails) + } + + if deleteError := mailRepo.DeleteFolder(folder); deleteError != nil { + return shortcuts.ServiceError(shortcuts.Internal, FolderDeletionFailed) + } + + return nil +} + +func buildFolderCounts(folders []mailModel.Folder) []FolderWithCount { + foldersWithCounts := make([]FolderWithCount, len(folders)) + for index, folder := range folders { + foldersWithCounts[index] = FolderWithCount{ + Folder: folder, + UnreadCount: mailRepo.CountUnreadByFolderID(folder.ID), + } + } + return foldersWithCounts +} + +func generateMessageID(senderAddress string) string { + parts := strings.Split(senderAddress, "@") + domainPart := "localhost" + if len(parts) > 1 { + domainPart = parts[1] + } + return fmt.Sprintf("<%d.%s@%s>", time.Now().UnixNano(), parts[0], domainPart) +} + +func generateFilename() string { + return fmt.Sprintf("%d", time.Now().UnixNano()) +} + +func readEmailBody(mailboxAddress string, folderSlug string, filename string) string { + content, readError := storage.ReadMailFile(mailboxAddress, folderSlug, filename) + if readError != nil { + logger.Warnf(LogPrefix, EmailFileReadFailed, readError) + return "" + } + return string(content) +} + +func deleteDraftWithFile(draftID uint, senderAddress string) { + draft := mailRepo.FindEmailByIDWithRelations(draftID) + if draft == nil { + return + } + storage.DeleteMailFile(senderAddress, "drafts", draft.Filename) + mailRepo.DeleteEmail(draft) +} + +var htmlTagPattern = regexp.MustCompile(`<[^>]*>`) + +func truncateSnippet(body string) string { + plainText := htmlTagPattern.ReplaceAllString(body, "") + plainText = strings.Join(strings.Fields(plainText), " ") + plainText = strings.TrimSpace(plainText) + if len(plainText) <= 200 { + return plainText + } + return plainText[:200] +} |
