101 lines
4.7 KiB
Go
101 lines
4.7 KiB
Go
package db
|
|
|
|
import "time"
|
|
|
|
// InsertEmailLog mirrors the EmailLog row creation in EmailRelay.log_email. Returns the
|
|
// new row's id (needed before recipient/attachment child rows can be inserted).
|
|
func (d *DB) InsertEmailLog(l EmailLog) (int64, error) {
|
|
res, err := d.Exec(`INSERT INTO esrv_email_logs
|
|
(message_id, timestamp, peer_ip, mail_from, mail_from_domain, to_address, cc_addresses, bcc_addresses, subject, email_headers, message_body, status, dkim_signed, username)
|
|
VALUES (?, ?, ?, ?, substr(?, instr(?, '@') + 1), ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
l.MessageID, l.Timestamp, l.PeerIP, l.MailFrom, l.MailFrom, l.MailFrom, l.ToAddress, l.CcAddresses, l.BccAddresses, l.Subject, l.EmailHeaders, l.MessageBody, l.Status, l.DKIMSigned, l.Username)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return res.LastInsertId()
|
|
}
|
|
|
|
// InsertEmailRecipientLog mirrors one EmailRecipientLog row creation.
|
|
func (d *DB) InsertEmailRecipientLog(l EmailRecipientLog) error {
|
|
_, err := d.Exec(`INSERT INTO esrv_email_recipient_logs
|
|
(email_log_id, recipient, recipient_type, status, error_code, error_message, server_response)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
l.EmailLogID, l.Recipient, l.RecipientType, l.Status, l.ErrorCode, l.ErrorMessage, l.ServerResponse)
|
|
return err
|
|
}
|
|
|
|
// UpdateEmailRecipientLogStatus resolves a placeholder "queued" recipient row (written
|
|
// at accept time, before the relay queue worker has actually attempted delivery) to
|
|
// its real outcome — matched by (email_log_id, recipient), not a stored row id, since
|
|
// EnqueueForDelivery never needs to thread individual recipient-log ids through the
|
|
// queue table. The status='queued' guard means this can never overwrite an
|
|
// already-resolved (e.g. local-delivery) row for the same address.
|
|
func (d *DB) UpdateEmailRecipientLogStatus(emailLogID int64, recipient, status, errorCode, errorMessage, serverResponse string) error {
|
|
_, err := d.Exec(`UPDATE esrv_email_recipient_logs
|
|
SET status = ?, error_code = ?, error_message = ?, server_response = ?
|
|
WHERE email_log_id = ? AND recipient = ? AND status = 'queued'`,
|
|
status, errorCode, errorMessage, serverResponse, emailLogID, recipient)
|
|
return err
|
|
}
|
|
|
|
// UpdateEmailLogStatus overwrites the parent esrv_email_logs row's stored overall
|
|
// status — called by the relay queue worker once a domain group resolves, so
|
|
// dashboard.html's "Recent Email Activity" (which reads this stored snapshot, not a
|
|
// live recompute) reflects the outcome instead of staying "queued" forever.
|
|
func (d *DB) UpdateEmailLogStatus(id int64, status string) error {
|
|
_, err := d.Exec(`UPDATE esrv_email_logs SET status = ? WHERE id = ?`, status, id)
|
|
return err
|
|
}
|
|
|
|
// PruneEmailLogsOlderThan deletes esrv_email_logs rows older than cutoff along with
|
|
// their esrv_email_recipient_logs and esrv_email_attachments rows (no FK cascade in
|
|
// this DB — see the schema comment on PRAGMA foreign_keys). A log still "queued" is
|
|
// never pruned regardless of age: that means the relay queue worker hasn't finished
|
|
// with it yet, and esrv_relay_queue's own row still references it. Returns the deleted
|
|
// attachments' file paths for the caller to remove from disk — this package does no
|
|
// file I/O — and the number of email logs deleted.
|
|
func (d *DB) PruneEmailLogsOlderThan(cutoff time.Time) (attachmentPaths []string, deleted int64, err error) {
|
|
rows, err := d.Query(`SELECT file_path FROM esrv_email_attachments
|
|
WHERE email_log_id IN (SELECT id FROM esrv_email_logs WHERE timestamp < ? AND status != 'queued')`, cutoff.UTC())
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
for rows.Next() {
|
|
var p string
|
|
if err := rows.Scan(&p); err != nil {
|
|
rows.Close()
|
|
return nil, 0, err
|
|
}
|
|
attachmentPaths = append(attachmentPaths, p)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
rows.Close()
|
|
return nil, 0, err
|
|
}
|
|
rows.Close()
|
|
|
|
if _, err := d.Exec(`DELETE FROM esrv_email_attachments
|
|
WHERE email_log_id IN (SELECT id FROM esrv_email_logs WHERE timestamp < ? AND status != 'queued')`, cutoff.UTC()); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if _, err := d.Exec(`DELETE FROM esrv_email_recipient_logs
|
|
WHERE email_log_id IN (SELECT id FROM esrv_email_logs WHERE timestamp < ? AND status != 'queued')`, cutoff.UTC()); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
res, err := d.Exec(`DELETE FROM esrv_email_logs WHERE timestamp < ? AND status != 'queued'`, cutoff.UTC())
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
deleted, err = res.RowsAffected()
|
|
return attachmentPaths, deleted, err
|
|
}
|
|
|
|
// InsertEmailAttachment mirrors one EmailAttachment row creation.
|
|
func (d *DB) InsertEmailAttachment(a EmailAttachment) error {
|
|
_, err := d.Exec(`INSERT INTO esrv_email_attachments
|
|
(email_log_id, filename, content_type, file_path, size, uploaded_at)
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
a.EmailLogID, a.Filename, a.ContentType, a.FilePath, a.Size, time.Now())
|
|
return err
|
|
}
|