package couch import ( "context" "fmt" "net/url" "time" "github.com/go-kivik/kivik/v4" _ "github.com/go-kivik/kivik/v4/couchdb" // The CouchDB driver "mail2couch/config" "mail2couch/mail" ) // Client wraps the Kivik client type Client struct { *kivik.Client } // MailDocument represents an email message stored in CouchDB type MailDocument struct { ID string `json:"_id,omitempty"` Rev string `json:"_rev,omitempty"` SourceUID string `json:"sourceUid"` // Unique ID from the mail source (e.g., IMAP UID) Mailbox string `json:"mailbox"` // Source mailbox name From []string `json:"from"` To []string `json:"to"` Subject string `json:"subject"` Date time.Time `json:"date"` Body string `json:"body"` Headers map[string][]string `json:"headers"` StoredAt time.Time `json:"storedAt"` // When the document was stored DocType string `json:"docType"` // Always "mail" } // NewClient creates a new CouchDB client from the configuration func NewClient(cfg *config.CouchDbConfig) (*Client, error) { parsedURL, err := url.Parse(cfg.URL) if err != nil { return nil, fmt.Errorf("invalid couchdb url: %w", err) } parsedURL.User = url.UserPassword(cfg.User, cfg.Password) dsn := parsedURL.String() client, err := kivik.New("couch", dsn) if err != nil { return nil, err } return &Client{client}, nil } // EnsureDB ensures that the configured database exists. func (c *Client) EnsureDB(ctx context.Context, dbName string) error { exists, err := c.DBExists(ctx, dbName) if err != nil { return err } if !exists { return c.CreateDB(ctx, dbName) } return nil } // ConvertMessage converts an IMAP message to a MailDocument func ConvertMessage(msg *mail.Message, mailbox string) *MailDocument { docID := fmt.Sprintf("%s_%d", mailbox, msg.UID) return &MailDocument{ ID: docID, SourceUID: fmt.Sprintf("%d", msg.UID), Mailbox: mailbox, From: msg.From, To: msg.To, Subject: msg.Subject, Date: msg.Date, Body: msg.Body, Headers: msg.Headers, StoredAt: time.Now(), DocType: "mail", } } // StoreMessage stores a mail message in CouchDB func (c *Client) StoreMessage(ctx context.Context, dbName string, doc *MailDocument) error { db := c.DB(dbName) if db.Err() != nil { return db.Err() } // Check if document already exists exists, err := c.DocumentExists(ctx, dbName, doc.ID) if err != nil { return fmt.Errorf("failed to check if document exists: %w", err) } if exists { return nil // Document already exists, skip } // Store the document _, err = db.Put(ctx, doc.ID, doc) if err != nil { return fmt.Errorf("failed to store document: %w", err) } return nil } // StoreMessages stores multiple mail messages in CouchDB func (c *Client) StoreMessages(ctx context.Context, dbName string, docs []*MailDocument) error { for _, doc := range docs { if err := c.StoreMessage(ctx, dbName, doc); err != nil { return err } } return nil } // DocumentExists checks if a document with the given ID already exists. func (c *Client) DocumentExists(ctx context.Context, dbName, docID string) (bool, error) { db := c.DB(dbName) if db.Err() != nil { return false, db.Err() } row := db.Get(ctx, docID) return row.Err() == nil, nil }