mirror of
https://github.com/neilalexander/yggmail.git
synced 2026-04-18 16:16:29 +03:00
231 lines
6.0 KiB
Go
231 lines
6.0 KiB
Go
package sqlite3
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/neilalexander/yggmail/internal/storage/types"
|
|
)
|
|
|
|
type TableMails struct {
|
|
db *sql.DB
|
|
selectMails *sql.Stmt
|
|
selectMail *sql.Stmt
|
|
selectMailNextID *sql.Stmt
|
|
selectIDForSeq *sql.Stmt
|
|
searchMail *sql.Stmt
|
|
createMail *sql.Stmt
|
|
countMails *sql.Stmt
|
|
countUnseenMails *sql.Stmt
|
|
updateMailFlags *sql.Stmt
|
|
deleteMail *sql.Stmt
|
|
expungeMail *sql.Stmt
|
|
}
|
|
|
|
const mailsSchema = `
|
|
CREATE TABLE IF NOT EXISTS mails (
|
|
mailbox TEXT NOT NULL,
|
|
id INTEGER NOT NULL DEFAULT 1,
|
|
mail BLOB NOT NULL,
|
|
datetime INTEGER NOT NULL,
|
|
seen BOOLEAN NOT NULL DEFAULT 0, -- the mail has been read
|
|
answered BOOLEAN NOT NULL DEFAULT 0, -- the mail has been replied to
|
|
flagged BOOLEAN NOT NULL DEFAULT 0, -- the mail has been flagged for later attention
|
|
deleted BOOLEAN NOT NULL DEFAULT 0, -- the email is marked for deletion at next EXPUNGE
|
|
PRIMARY KEY (mailbox, id),
|
|
FOREIGN KEY (mailbox) REFERENCES mailboxes(mailbox) ON DELETE CASCADE ON UPDATE CASCADE
|
|
);
|
|
|
|
CREATE VIEW IF NOT EXISTS inboxes AS SELECT * FROM (
|
|
SELECT ROW_NUMBER() OVER (PARTITION BY mailbox) AS seq, * FROM mails
|
|
)
|
|
ORDER BY mailbox, id;
|
|
`
|
|
|
|
const selectMailsStmt = `
|
|
SELECT * FROM inboxes
|
|
ORDER BY mailbox, id
|
|
`
|
|
|
|
const selectMailStmt = `
|
|
SELECT seq, id, mail, datetime, seen, answered, flagged, deleted FROM inboxes
|
|
WHERE mailbox = $1 AND id = $2
|
|
ORDER BY mailbox, id
|
|
`
|
|
|
|
const selectMailCountStmt = `
|
|
SELECT COUNT(*) FROM mails WHERE mailbox = $1
|
|
`
|
|
|
|
const selectMailUnseenStmt = `
|
|
SELECT COUNT(*) FROM mails WHERE mailbox = $1 AND seen = 0
|
|
`
|
|
|
|
const searchMailStmt = `
|
|
SELECT id FROM mails
|
|
WHERE mailbox = $1
|
|
ORDER BY mailbox, id
|
|
`
|
|
|
|
const insertMailStmt = `
|
|
INSERT INTO mails (mailbox, id, mail, datetime) VALUES(
|
|
$1, (
|
|
SELECT IFNULL(MAX(id)+1,1) AS id FROM mails
|
|
WHERE mailbox = $1
|
|
), $2, $3
|
|
)
|
|
RETURNING id;
|
|
`
|
|
|
|
const selectIDForSeqStmt = `
|
|
SELECT id FROM inboxes
|
|
WHERE mailbox = $1 AND seq = $2
|
|
`
|
|
|
|
const selectMailNextID = `
|
|
SELECT IFNULL(MAX(id)+1,1) AS id FROM mails
|
|
WHERE mailbox = $1
|
|
`
|
|
|
|
const updateMailFlagsStmt = `
|
|
UPDATE mails SET seen = $1, answered = $2, flagged = $3, deleted = $4 WHERE mailbox = $5 AND id = $6
|
|
`
|
|
|
|
const deleteMailStmt = `
|
|
UPDATE mails SET deleted = 1 WHERE mailbox = $1 AND id = $2
|
|
`
|
|
|
|
const expungeMailStmt = `
|
|
DELETE FROM mails WHERE mailbox = $1 AND deleted = 1
|
|
`
|
|
|
|
func NewTableMails(db *sql.DB) (*TableMails, error) {
|
|
t := &TableMails{
|
|
db: db,
|
|
}
|
|
_, err := db.Exec(mailsSchema)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Exec: %w", err)
|
|
}
|
|
t.selectMails, err = db.Prepare(selectMailsStmt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(selectMailsStmt): %w", err)
|
|
}
|
|
t.selectMail, err = db.Prepare(selectMailStmt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(selectMailStmt): %w", err)
|
|
}
|
|
t.selectMailNextID, err = db.Prepare(selectMailNextID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(selectMailNextID): %w", err)
|
|
}
|
|
t.selectIDForSeq, err = db.Prepare(selectIDForSeqStmt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(selectPIDForIDStmt): %w", err)
|
|
}
|
|
t.searchMail, err = db.Prepare(searchMailStmt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(selectPIDForIDStmt): %w", err)
|
|
}
|
|
t.createMail, err = db.Prepare(insertMailStmt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(insertMailStmt): %w", err)
|
|
}
|
|
t.updateMailFlags, err = db.Prepare(updateMailFlagsStmt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(updateMailSeenStmt): %w", err)
|
|
}
|
|
t.deleteMail, err = db.Prepare(deleteMailStmt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(deleteMailStmt): %w", err)
|
|
}
|
|
t.expungeMail, err = db.Prepare(expungeMailStmt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(expungeMailStmt): %w", err)
|
|
}
|
|
t.countMails, err = db.Prepare(selectMailCountStmt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(selectMailCountStmt): %w", err)
|
|
}
|
|
t.countUnseenMails, err = db.Prepare(selectMailUnseenStmt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("db.Prepare(selectMailUnseenStmt): %w", err)
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
func (t *TableMails) MailCreate(mailbox string, data []byte) (int, error) {
|
|
var id int
|
|
err := t.createMail.QueryRow(mailbox, data, time.Now().Unix()).Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
func (t *TableMails) MailSelect(mailbox string, id int) (int, *types.Mail, error) {
|
|
var seq int
|
|
var datetime int64
|
|
mail := &types.Mail{}
|
|
err := t.selectMail.QueryRow(mailbox, id).Scan(
|
|
&seq, &mail.ID, &mail.Mail, &datetime,
|
|
&mail.Seen, &mail.Answered, &mail.Flagged, &mail.Deleted,
|
|
)
|
|
mail.Date = time.Unix(datetime, 0)
|
|
return seq, mail, err
|
|
}
|
|
|
|
func (t *TableMails) MailSearch(mailbox string) ([]uint32, error) {
|
|
var ids []uint32
|
|
rows, err := t.searchMail.Query(mailbox)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("t.searchMail.Query: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var id uint32
|
|
if err := rows.Scan(&id); err != nil {
|
|
return nil, fmt.Errorf("rows.Scan: %w", err)
|
|
}
|
|
ids = append(ids, id)
|
|
}
|
|
return ids, nil
|
|
}
|
|
|
|
func (t *TableMails) MailNextID(mailbox string) (int, error) {
|
|
var id int
|
|
err := t.selectMailNextID.QueryRow(mailbox).Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
func (t *TableMails) MailIDForSeq(mailbox string, seq int) (int, error) {
|
|
var id int
|
|
err := t.selectIDForSeq.QueryRow(mailbox, seq).Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
func (t *TableMails) MailUnseen(mailbox string) (int, error) {
|
|
var unseen int
|
|
err := t.countUnseenMails.QueryRow(mailbox).Scan(&unseen)
|
|
return unseen, err
|
|
}
|
|
|
|
func (t *TableMails) MailUpdateFlags(mailbox string, id int, seen, answered, flagged, deleted bool) error {
|
|
_, err := t.updateMailFlags.Exec(seen, answered, flagged, deleted, mailbox, id)
|
|
return err
|
|
}
|
|
|
|
func (t *TableMails) MailDelete(mailbox string, id int) error {
|
|
_, err := t.deleteMail.Exec(mailbox, id)
|
|
return err
|
|
}
|
|
|
|
func (t *TableMails) MailExpunge(mailbox string) error {
|
|
_, err := t.expungeMail.Exec(mailbox)
|
|
return err
|
|
}
|
|
|
|
func (t *TableMails) MailCount(mailbox string) (int, error) {
|
|
var count int
|
|
err := t.countMails.QueryRow(mailbox).Scan(&count)
|
|
return count, err
|
|
}
|