mirror of
https://github.com/Dvorinka/Productier.git
synced 2026-06-04 12:33:01 +00:00
454 lines
12 KiB
Go
454 lines
12 KiB
Go
package store
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type Mailbox struct {
|
|
ID string `json:"id"`
|
|
WorkspaceSlug string `json:"workspaceSlug"`
|
|
Label string `json:"label"`
|
|
Email string `json:"email"`
|
|
DisplayName string `json:"displayName"`
|
|
IMAPHost string `json:"imapHost"`
|
|
IMAPPort int `json:"imapPort"`
|
|
IMAPUsername string `json:"imapUsername"`
|
|
IMAPUseTLS bool `json:"imapUseTls"`
|
|
SMTPHost string `json:"smtpHost"`
|
|
SMTPPort int `json:"smtpPort"`
|
|
SMTPUsername string `json:"smtpUsername"`
|
|
SMTPUseTLS bool `json:"smtpUseTls"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
LastSyncedAt *time.Time `json:"lastSyncedAt,omitempty"`
|
|
SyncStatus string `json:"syncStatus"`
|
|
SyncError string `json:"syncError,omitempty"`
|
|
}
|
|
|
|
type MailAddress struct {
|
|
Name string `json:"name"`
|
|
Email string `json:"email"`
|
|
}
|
|
|
|
type MailMessage struct {
|
|
ID string `json:"id"`
|
|
WorkspaceSlug string `json:"workspaceSlug"`
|
|
MailboxID string `json:"mailboxId"`
|
|
RemoteUID int64 `json:"remoteUid"`
|
|
MessageID string `json:"messageId"`
|
|
Folder string `json:"folder"`
|
|
From MailAddress `json:"from"`
|
|
To []MailAddress `json:"to"`
|
|
Cc []MailAddress `json:"cc"`
|
|
Subject string `json:"subject"`
|
|
Snippet string `json:"snippet"`
|
|
TextBody string `json:"textBody"`
|
|
HTMLBody string `json:"htmlBody"`
|
|
ReceivedAt time.Time `json:"receivedAt"`
|
|
IsRead bool `json:"isRead"`
|
|
LinkedTaskID *string `json:"linkedTaskId,omitempty"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
type OutgoingMail struct {
|
|
ID string `json:"id"`
|
|
WorkspaceSlug string `json:"workspaceSlug"`
|
|
MailboxID string `json:"mailboxId"`
|
|
To []MailAddress `json:"to"`
|
|
Cc []MailAddress `json:"cc"`
|
|
Bcc []MailAddress `json:"bcc"`
|
|
Subject string `json:"subject"`
|
|
TextBody string `json:"textBody"`
|
|
HTMLBody string `json:"htmlBody"`
|
|
Status string `json:"status"`
|
|
ScheduledFor *time.Time `json:"scheduledFor,omitempty"`
|
|
SentAt *time.Time `json:"sentAt,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
type MailboxConnection struct {
|
|
Mailbox
|
|
IMAPPasswordCiphertext string
|
|
SMTPPasswordCiphertext string
|
|
}
|
|
|
|
type CreateMailboxRecordInput struct {
|
|
WorkspaceSlug string
|
|
Label string
|
|
Email string
|
|
DisplayName string
|
|
IMAPHost string
|
|
IMAPPort int
|
|
IMAPUsername string
|
|
IMAPPasswordCiphertext string
|
|
IMAPUseTLS bool
|
|
SMTPHost string
|
|
SMTPPort int
|
|
SMTPUsername string
|
|
SMTPPasswordCiphertext string
|
|
SMTPUseTLS bool
|
|
}
|
|
|
|
type UpdateMailboxSyncStatusInput struct {
|
|
SyncStatus string
|
|
SyncError *string
|
|
LastSyncedAt *time.Time
|
|
}
|
|
|
|
type InboundMailMessage struct {
|
|
WorkspaceSlug string
|
|
MailboxID string
|
|
RemoteUID int64
|
|
MessageID string
|
|
Folder string
|
|
From MailAddress
|
|
To []MailAddress
|
|
Cc []MailAddress
|
|
Subject string
|
|
Snippet string
|
|
TextBody string
|
|
HTMLBody string
|
|
ReceivedAt time.Time
|
|
IsRead bool
|
|
}
|
|
|
|
type CreateOutgoingMailInput struct {
|
|
WorkspaceSlug string
|
|
MailboxID string
|
|
To []MailAddress
|
|
Cc []MailAddress
|
|
Bcc []MailAddress
|
|
Subject string
|
|
TextBody string
|
|
HTMLBody string
|
|
Status string
|
|
ScheduledFor *time.Time
|
|
}
|
|
|
|
type UpdateOutgoingMailStatusInput struct {
|
|
Status string
|
|
SentAt *time.Time
|
|
Error *string
|
|
}
|
|
|
|
func (s *State) ListAllMailboxes() []Mailbox {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return append([]Mailbox(nil), s.Mailboxes...)
|
|
}
|
|
|
|
func (s *State) ListMailboxes(workspaceSlug string) []Mailbox {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return filterByWorkspace(s.Mailboxes, workspaceSlug)
|
|
}
|
|
|
|
func (s *State) GetMailboxByID(mailboxID string) (Mailbox, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
for _, mailbox := range s.Mailboxes {
|
|
if mailbox.ID == mailboxID {
|
|
return mailbox, nil
|
|
}
|
|
}
|
|
|
|
return Mailbox{}, errors.New("mailbox not found")
|
|
}
|
|
|
|
func (s *State) GetMailboxConnection(mailboxID string) (MailboxConnection, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
connection, ok := s.MailboxAuth[mailboxID]
|
|
if !ok {
|
|
return MailboxConnection{}, errors.New("mailbox connection not found")
|
|
}
|
|
|
|
return connection, nil
|
|
}
|
|
|
|
func (s *State) CreateMailbox(input CreateMailboxRecordInput) (Mailbox, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
now := time.Now().UTC()
|
|
mailbox := Mailbox{
|
|
ID: uuid.NewString(),
|
|
WorkspaceSlug: input.WorkspaceSlug,
|
|
Label: input.Label,
|
|
Email: input.Email,
|
|
DisplayName: input.DisplayName,
|
|
IMAPHost: input.IMAPHost,
|
|
IMAPPort: input.IMAPPort,
|
|
IMAPUsername: input.IMAPUsername,
|
|
IMAPUseTLS: input.IMAPUseTLS,
|
|
SMTPHost: input.SMTPHost,
|
|
SMTPPort: input.SMTPPort,
|
|
SMTPUsername: input.SMTPUsername,
|
|
SMTPUseTLS: input.SMTPUseTLS,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
SyncStatus: "idle",
|
|
}
|
|
s.Mailboxes = append([]Mailbox{mailbox}, s.Mailboxes...)
|
|
s.MailboxAuth[mailbox.ID] = MailboxConnection{
|
|
Mailbox: mailbox,
|
|
IMAPPasswordCiphertext: input.IMAPPasswordCiphertext,
|
|
SMTPPasswordCiphertext: input.SMTPPasswordCiphertext,
|
|
}
|
|
s.appendActivityLocked(input.WorkspaceSlug, "Mailbox connected", fmt.Sprintf("%s is ready for sync.", input.Email))
|
|
return mailbox, nil
|
|
}
|
|
|
|
func (s *State) UpdateMailboxSyncStatus(mailboxID string, input UpdateMailboxSyncStatusInput) (Mailbox, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for index, mailbox := range s.Mailboxes {
|
|
if mailbox.ID != mailboxID {
|
|
continue
|
|
}
|
|
|
|
if input.SyncStatus != "" {
|
|
mailbox.SyncStatus = input.SyncStatus
|
|
}
|
|
if input.SyncError != nil {
|
|
mailbox.SyncError = *input.SyncError
|
|
}
|
|
if input.LastSyncedAt != nil {
|
|
mailbox.LastSyncedAt = input.LastSyncedAt
|
|
}
|
|
mailbox.UpdatedAt = time.Now().UTC()
|
|
s.Mailboxes[index] = mailbox
|
|
if connection, ok := s.MailboxAuth[mailboxID]; ok {
|
|
connection.Mailbox = mailbox
|
|
s.MailboxAuth[mailboxID] = connection
|
|
}
|
|
return mailbox, nil
|
|
}
|
|
|
|
return Mailbox{}, errors.New("mailbox not found")
|
|
}
|
|
|
|
func (s *State) ListMailMessages(workspaceSlug string, mailboxID string) []MailMessage {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
items := filterByWorkspace(s.MailMessages, workspaceSlug)
|
|
if mailboxID == "" {
|
|
return items
|
|
}
|
|
|
|
filtered := make([]MailMessage, 0, len(items))
|
|
for _, item := range items {
|
|
if item.MailboxID == mailboxID {
|
|
filtered = append(filtered, item)
|
|
}
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
func (s *State) GetMailMessageByID(messageID string) (MailMessage, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
for _, message := range s.MailMessages {
|
|
if message.ID == messageID {
|
|
return message, nil
|
|
}
|
|
}
|
|
|
|
return MailMessage{}, errors.New("mail message not found")
|
|
}
|
|
|
|
func (s *State) UpsertMailMessages(mailboxID string, messages []InboundMailMessage) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
now := time.Now().UTC()
|
|
for _, input := range messages {
|
|
found := false
|
|
for index, message := range s.MailMessages {
|
|
if message.MailboxID == mailboxID && message.Folder == input.Folder && message.RemoteUID == input.RemoteUID {
|
|
linkedTaskID := message.LinkedTaskID
|
|
s.MailMessages[index] = MailMessage{
|
|
ID: message.ID,
|
|
WorkspaceSlug: input.WorkspaceSlug,
|
|
MailboxID: mailboxID,
|
|
RemoteUID: input.RemoteUID,
|
|
MessageID: input.MessageID,
|
|
Folder: input.Folder,
|
|
From: input.From,
|
|
To: input.To,
|
|
Cc: input.Cc,
|
|
Subject: input.Subject,
|
|
Snippet: input.Snippet,
|
|
TextBody: input.TextBody,
|
|
HTMLBody: input.HTMLBody,
|
|
ReceivedAt: input.ReceivedAt,
|
|
IsRead: input.IsRead,
|
|
LinkedTaskID: linkedTaskID,
|
|
CreatedAt: message.CreatedAt,
|
|
UpdatedAt: now,
|
|
}
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if found {
|
|
continue
|
|
}
|
|
s.MailMessages = append([]MailMessage{{
|
|
ID: uuid.NewString(),
|
|
WorkspaceSlug: input.WorkspaceSlug,
|
|
MailboxID: mailboxID,
|
|
RemoteUID: input.RemoteUID,
|
|
MessageID: input.MessageID,
|
|
Folder: input.Folder,
|
|
From: input.From,
|
|
To: input.To,
|
|
Cc: input.Cc,
|
|
Subject: input.Subject,
|
|
Snippet: input.Snippet,
|
|
TextBody: input.TextBody,
|
|
HTMLBody: input.HTMLBody,
|
|
ReceivedAt: input.ReceivedAt,
|
|
IsRead: input.IsRead,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}}, s.MailMessages...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *State) LinkMailMessageTask(messageID string, taskID string) (MailMessage, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for index, message := range s.MailMessages {
|
|
if message.ID != messageID {
|
|
continue
|
|
}
|
|
message.LinkedTaskID = &taskID
|
|
message.UpdatedAt = time.Now().UTC()
|
|
s.MailMessages[index] = message
|
|
return message, nil
|
|
}
|
|
|
|
return MailMessage{}, errors.New("mail message not found")
|
|
}
|
|
|
|
func (s *State) ListOutgoingMails(workspaceSlug string, mailboxID string) []OutgoingMail {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
items := filterByWorkspace(s.OutgoingMails, workspaceSlug)
|
|
if mailboxID == "" {
|
|
return items
|
|
}
|
|
|
|
filtered := make([]OutgoingMail, 0, len(items))
|
|
for _, item := range items {
|
|
if item.MailboxID == mailboxID {
|
|
filtered = append(filtered, item)
|
|
}
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
func (s *State) ListDueOutgoingMails(now time.Time, limit int) []OutgoingMail {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
items := make([]OutgoingMail, 0, limit)
|
|
for _, item := range s.OutgoingMails {
|
|
if item.Status != "queued" && item.Status != "scheduled" {
|
|
continue
|
|
}
|
|
if item.ScheduledFor != nil && item.ScheduledFor.After(now) {
|
|
continue
|
|
}
|
|
items = append(items, item)
|
|
if limit > 0 && len(items) >= limit {
|
|
break
|
|
}
|
|
}
|
|
return items
|
|
}
|
|
|
|
func (s *State) GetOutgoingMailByID(outgoingMailID string) (OutgoingMail, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
for _, item := range s.OutgoingMails {
|
|
if item.ID == outgoingMailID {
|
|
return item, nil
|
|
}
|
|
}
|
|
|
|
return OutgoingMail{}, errors.New("outgoing mail not found")
|
|
}
|
|
|
|
func (s *State) CreateOutgoingMail(input CreateOutgoingMailInput) (OutgoingMail, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
now := time.Now().UTC()
|
|
item := OutgoingMail{
|
|
ID: uuid.NewString(),
|
|
WorkspaceSlug: input.WorkspaceSlug,
|
|
MailboxID: input.MailboxID,
|
|
To: input.To,
|
|
Cc: input.Cc,
|
|
Bcc: input.Bcc,
|
|
Subject: input.Subject,
|
|
TextBody: input.TextBody,
|
|
HTMLBody: input.HTMLBody,
|
|
Status: input.Status,
|
|
ScheduledFor: input.ScheduledFor,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
s.OutgoingMails = append([]OutgoingMail{item}, s.OutgoingMails...)
|
|
s.appendActivityLocked(input.WorkspaceSlug, "Outgoing mail queued", input.Subject)
|
|
return item, nil
|
|
}
|
|
|
|
func (s *State) UpdateOutgoingMailStatus(outgoingMailID string, input UpdateOutgoingMailStatusInput) (OutgoingMail, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for index, item := range s.OutgoingMails {
|
|
if item.ID != outgoingMailID {
|
|
continue
|
|
}
|
|
if input.Status != "" {
|
|
item.Status = input.Status
|
|
}
|
|
if input.SentAt != nil {
|
|
item.SentAt = input.SentAt
|
|
}
|
|
if input.Error != nil {
|
|
item.Error = *input.Error
|
|
}
|
|
item.UpdatedAt = time.Now().UTC()
|
|
s.OutgoingMails[index] = item
|
|
return item, nil
|
|
}
|
|
|
|
return OutgoingMail{}, errors.New("outgoing mail not found")
|
|
}
|
|
|
|
func (item Mailbox) GetWorkspaceSlug() string { return item.WorkspaceSlug }
|
|
func (item MailMessage) GetWorkspaceSlug() string { return item.WorkspaceSlug }
|
|
func (item OutgoingMail) GetWorkspaceSlug() string { return item.WorkspaceSlug }
|