summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2025-12-23 10:55:56 +0530
committerBobby <[email protected]>2025-12-23 10:55:56 +0530
commit078a60e33af6e6013fe7b86643180e7d13aa63d0 (patch)
tree32d9e90e9305e947dc20a7a04023205430522bbc
parent318360a60aa52cf91ac80d547285f4d14c2c4517 (diff)
downloadlain-078a60e33af6e6013fe7b86643180e7d13aa63d0.tar.xz
lain-078a60e33af6e6013fe7b86643180e7d13aa63d0.zip
email utils and functions
-rw-r--r--controllers/mail.go34
-rw-r--r--repository/folders.go232
-rw-r--r--types/email.go5
-rw-r--r--utils/email/folders.go30
4 files changed, 301 insertions, 0 deletions
diff --git a/controllers/mail.go b/controllers/mail.go
index 2d32936..da2adf2 100644
--- a/controllers/mail.go
+++ b/controllers/mail.go
@@ -1 +1,35 @@
package controllers
+
+import (
+ "lain/repository"
+ "lain/session"
+ "lain/utils/meta"
+ "lain/utils/shortcuts"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func Mailbox(context *fiber.Ctx) error {
+ folderPath := context.Params("*", "inbox")
+ if folderPath == "" {
+ folderPath = "inbox"
+ }
+
+ email, err := session.GetSessionEmail(context)
+ if err != nil {
+ return InternalServerError(context, err)
+ }
+
+ folders := repository.GetFolders(email, folderPath)
+ displayName := repository.GetFolderDisplayName(email, folderPath)
+
+ emails := []fiber.Map{}
+
+ meta.SetPageTitle(context, displayName)
+
+ return shortcuts.Render(context, "mail/folder", fiber.Map{
+ "Folders": folders,
+ "Emails": emails,
+ "Email": nil,
+ })
+}
diff --git a/repository/folders.go b/repository/folders.go
new file mode 100644
index 0000000..973727a
--- /dev/null
+++ b/repository/folders.go
@@ -0,0 +1,232 @@
+package repository
+
+import (
+ "lain/database"
+ "lain/models"
+ "lain/utils/crypto"
+ "lain/utils/email"
+ "net/url"
+ "strings"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+const defaultFolderIcon = "/static/images/icons/folder.png"
+
+func GetFolders(userEmail, activeFolder string) []fiber.Map {
+ syncFolders(userEmail)
+
+ var allFolders []models.Folder
+ database.DB.Where("user_email = ?", userEmail).Find(&allFolders)
+
+ sortFolders(allFolders)
+
+ folderMap := make(map[uint]*fiber.Map)
+ var rootFolders []fiber.Map
+
+ for _, folder := range allFolders {
+ icon := folder.Icon
+ if icon == "" {
+ icon = defaultFolderIcon
+ }
+
+ displayName := getDisplayName(folder.IMAPName)
+
+ folderData := fiber.Map{
+ "ID": folder.ID,
+ "Name": displayName,
+ "IMAPName": folder.IMAPName,
+ "Icon": icon,
+ "UnreadCount": folder.UnreadCount,
+ "Active": false,
+ "ParentID": folder.ParentID,
+ "SortOrder": folder.SortOrder,
+ "Subfolders": []fiber.Map{},
+ }
+ folderMap[folder.ID] = &folderData
+ }
+
+ for _, folder := range allFolders {
+ folderData := folderMap[folder.ID]
+ if folder.ParentID == nil {
+ rootFolders = append(rootFolders, *folderData)
+ } else {
+ if parent, ok := folderMap[*folder.ParentID]; ok {
+ subfolders := (*parent)["Subfolders"].([]fiber.Map)
+ (*parent)["Subfolders"] = append(subfolders, *folderData)
+ }
+ }
+ }
+
+ return updateActiveFolder(rootFolders, activeFolder)
+}
+
+func GetFolderDisplayName(userEmail, folderPath string) string {
+ decodedPath, _ := url.QueryUnescape(folderPath)
+
+ var folder models.Folder
+ err := database.DB.Where("user_email = ? AND LOWER(imap_name) = ?", userEmail, strings.ToLower(decodedPath)).First(&folder).Error
+
+ if err != nil {
+ if strings.ToLower(decodedPath) == "inbox" {
+ return "Inbox"
+ }
+ return decodedPath
+ }
+
+ return getDisplayName(folder.IMAPName)
+}
+
+func getDisplayName(imapName string) string {
+ if strings.Contains(imapName, "/") {
+ parts := strings.Split(imapName, "/")
+ lastPart := parts[len(parts)-1]
+ if strings.ToLower(lastPart) == "inbox" {
+ return "Inbox"
+ }
+ return lastPart
+ }
+
+ if strings.ToLower(imapName) == "inbox" {
+ return "Inbox"
+ }
+
+ return imapName
+}
+
+func updateActiveFolder(folders []fiber.Map, activeFolder string) []fiber.Map {
+ decodedActive, _ := url.QueryUnescape(activeFolder)
+ activeLower := strings.ToLower(decodedActive)
+
+ var updateActive func([]fiber.Map)
+ updateActive = func(folderList []fiber.Map) {
+ for i := range folderList {
+ imapNameLower := strings.ToLower(folderList[i]["IMAPName"].(string))
+ folderList[i]["Active"] = imapNameLower == activeLower
+ if subfolders, ok := folderList[i]["Subfolders"].([]fiber.Map); ok && len(subfolders) > 0 {
+ updateActive(subfolders)
+ }
+ }
+ }
+
+ updateActive(folders)
+ return folders
+}
+
+func sortFolders(folders []models.Folder) {
+ for i := 0; i < len(folders)-1; i++ {
+ for j := 0; j < len(folders)-i-1; j++ {
+ if folders[j].SortOrder > folders[j+1].SortOrder {
+ folders[j], folders[j+1] = folders[j+1], folders[j]
+ } else if folders[j].SortOrder == folders[j+1].SortOrder {
+ if strings.ToLower(folders[j].IMAPName) > strings.ToLower(folders[j+1].IMAPName) {
+ folders[j], folders[j+1] = folders[j+1], folders[j]
+ }
+ }
+ }
+ }
+}
+
+func syncFolders(userEmail string) error {
+ var prefs models.Preferences
+ if err := database.DB.Where("email = ?", userEmail).First(&prefs).Error; err != nil {
+ return err
+ }
+
+ password, err := crypto.Decrypt(prefs.Authorization)
+ if err != nil {
+ return err
+ }
+
+ client, err := email.ConnectIMAP(userEmail, password)
+ if err != nil {
+ return err
+ }
+ defer email.DisconnectIMAP(client)
+
+ imapFolders, err := email.FetchFolders(client)
+ if err != nil {
+ return err
+ }
+
+ foldersByName := make(map[string]uint)
+
+ for i, imapFolder := range imapFolders {
+ var folder models.Folder
+ imapNameLower := strings.ToLower(imapFolder.Name)
+ result := database.DB.Where("user_email = ? AND LOWER(imap_name) = ?", userEmail, imapNameLower).First(&folder)
+
+ sortOrder := getSortOrder(imapFolder.Name, i)
+
+ if result.Error != nil {
+ folder = models.Folder{
+ UserEmail: userEmail,
+ Name: imapFolder.Name,
+ IMAPName: imapFolder.Name,
+ Icon: defaultFolderIcon,
+ SortOrder: sortOrder,
+ }
+ database.DB.Create(&folder)
+ foldersByName[imapNameLower] = folder.ID
+ } else {
+ folder.Name = imapFolder.Name
+ folder.SortOrder = sortOrder
+ if folder.Icon == "" {
+ folder.Icon = defaultFolderIcon
+ }
+ database.DB.Save(&folder)
+ foldersByName[imapNameLower] = folder.ID
+ }
+ }
+
+ for _, imapFolder := range imapFolders {
+ if strings.Contains(imapFolder.Name, "/") {
+ parts := strings.Split(imapFolder.Name, "/")
+ if len(parts) > 1 {
+ parentName := strings.Join(parts[:len(parts)-1], "/")
+ parentNameLower := strings.ToLower(parentName)
+ if parentID, ok := foldersByName[parentNameLower]; ok {
+ var folder models.Folder
+ imapNameLower := strings.ToLower(imapFolder.Name)
+ if err := database.DB.Where("user_email = ? AND LOWER(imap_name) = ?", userEmail, imapNameLower).First(&folder).Error; err == nil {
+ folder.ParentID = &parentID
+ database.DB.Save(&folder)
+ }
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func getSortOrder(folderName string, index int) int {
+ nameLower := strings.ToLower(folderName)
+
+ if nameLower == "inbox" {
+ return 0
+ }
+ if strings.Contains(nameLower, "draft") {
+ return 1
+ }
+ if strings.Contains(nameLower, "sent") {
+ return 2
+ }
+ if strings.Contains(nameLower, "archive") {
+ return 3
+ }
+ if strings.Contains(nameLower, "trash") || strings.Contains(nameLower, "deleted") {
+ return 4
+ }
+ if strings.Contains(nameLower, "spam") || strings.Contains(nameLower, "junk") {
+ return 5
+ }
+
+ if strings.Contains(folderName, "/") {
+ parts := strings.Split(folderName, "/")
+ baseOrder := getSortOrder(parts[0], index)
+ return baseOrder + 1000 + (index * 10)
+ }
+
+ return 100 + index
+}
diff --git a/types/email.go b/types/email.go
index aaf3376..d5cc8f5 100644
--- a/types/email.go
+++ b/types/email.go
@@ -5,3 +5,8 @@ import "github.com/emersion/go-imap/client"
type EmailClient struct {
*client.Client
}
+
+type IMAPFolder struct {
+ Name string
+ HasChildren bool
+}
diff --git a/utils/email/folders.go b/utils/email/folders.go
new file mode 100644
index 0000000..3acffae
--- /dev/null
+++ b/utils/email/folders.go
@@ -0,0 +1,30 @@
+package email
+
+import (
+ "lain/types"
+
+ "github.com/emersion/go-imap"
+)
+
+func FetchFolders(client *types.EmailClient) ([]types.IMAPFolder, error) {
+ mailboxes := make(chan *imap.MailboxInfo, 10)
+ done := make(chan error, 1)
+
+ go func() {
+ done <- client.List("", "*", mailboxes)
+ }()
+
+ var folders []types.IMAPFolder
+ for m := range mailboxes {
+ folders = append(folders, types.IMAPFolder{
+ Name: m.Name,
+ HasChildren: hasAttribute(m.Attributes, "\\HasChildren"),
+ })
+ }
+
+ if err := <-done; err != nil {
+ return nil, err
+ }
+
+ return folders, nil
+}