Files
sgmail-skill/internal/db/db.go
2026-06-06 22:28:36 +08:00

165 lines
4.4 KiB
Go

package db
import (
"context"
"database/sql"
"fmt"
"os"
"path/filepath"
_ "modernc.org/sqlite"
)
type Store struct {
DB *sql.DB
}
func Open(path string) (*Store, error) {
if path != ":memory:" {
if err := os.MkdirAll(filepath.Dir(path), 0750); err != nil {
return nil, fmt.Errorf("create database directory: %w", err)
}
}
db, err := sql.Open("sqlite", path)
if err != nil {
return nil, fmt.Errorf("open sqlite: %w", err)
}
if _, err := db.Exec("PRAGMA foreign_keys = ON"); err != nil {
_ = db.Close()
return nil, fmt.Errorf("enable foreign keys: %w", err)
}
if path == ":memory:" {
db.SetMaxOpenConns(1)
}
return &Store{DB: db}, nil
}
func (s *Store) Close() error {
if s == nil || s.DB == nil {
return nil
}
return s.DB.Close()
}
func (s *Store) Ping(ctx context.Context) error {
return s.DB.PingContext(ctx)
}
func (s *Store) Migrate(ctx context.Context) error {
_, err := s.DB.ExecContext(ctx, schemaSQL)
if err != nil {
return fmt.Errorf("migrate sqlite: %w", err)
}
return nil
}
const schemaSQL = `
CREATE TABLE IF NOT EXISTS mail_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id TEXT NOT NULL,
pop3_uid TEXT NOT NULL,
message_id_header TEXT,
in_reply_to TEXT,
references_header TEXT,
subject TEXT,
sender_name TEXT,
sender_email TEXT,
to_json TEXT NOT NULL DEFAULT '[]',
cc_json TEXT NOT NULL DEFAULT '[]',
date_header TEXT,
sent_at TEXT,
received_at TEXT NOT NULL,
body_text TEXT,
body_html TEXT,
raw_eml_path TEXT,
has_attachment INTEGER NOT NULL DEFAULT 0,
local_read INTEGER NOT NULL DEFAULT 0,
local_processed INTEGER NOT NULL DEFAULT 0,
local_archived INTEGER NOT NULL DEFAULT 0,
local_deleted INTEGER NOT NULL DEFAULT 0,
local_replied INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(account_id, pop3_uid)
);
CREATE INDEX IF NOT EXISTS idx_mail_messages_account_uid ON mail_messages(account_id, pop3_uid);
CREATE INDEX IF NOT EXISTS idx_mail_messages_message_id ON mail_messages(message_id_header);
CREATE INDEX IF NOT EXISTS idx_mail_messages_sender ON mail_messages(sender_email);
CREATE INDEX IF NOT EXISTS idx_mail_messages_sent_at ON mail_messages(sent_at);
CREATE INDEX IF NOT EXISTS idx_mail_messages_status ON mail_messages(local_read, local_processed, local_archived, local_deleted, local_replied);
CREATE TABLE IF NOT EXISTS attachments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id INTEGER NOT NULL,
file_name TEXT NOT NULL,
content_type TEXT,
content_id TEXT,
size_bytes INTEGER NOT NULL DEFAULT 0,
storage_path TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY(message_id) REFERENCES mail_messages(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_attachments_message ON attachments(message_id);
CREATE TABLE IF NOT EXISTS labels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
color TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS message_labels (
message_id INTEGER NOT NULL,
label_id INTEGER NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY(message_id, label_id),
FOREIGN KEY(message_id) REFERENCES mail_messages(id) ON DELETE CASCADE,
FOREIGN KEY(label_id) REFERENCES labels(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_message_labels_label ON message_labels(label_id);
CREATE TABLE IF NOT EXISTS drafts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
to_json TEXT NOT NULL DEFAULT '[]',
cc_json TEXT NOT NULL DEFAULT '[]',
bcc_json TEXT NOT NULL DEFAULT '[]',
subject TEXT,
body TEXT,
reply_to_email_id INTEGER,
status TEXT NOT NULL DEFAULT 'draft',
last_error TEXT,
sent_message_id INTEGER,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(reply_to_email_id) REFERENCES mail_messages(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_drafts_status ON drafts(status);
CREATE TABLE IF NOT EXISTS sent_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
draft_id INTEGER,
to_json TEXT NOT NULL DEFAULT '[]',
cc_json TEXT NOT NULL DEFAULT '[]',
bcc_json TEXT NOT NULL DEFAULT '[]',
subject TEXT,
body TEXT,
message_id_header TEXT,
in_reply_to TEXT,
references_header TEXT,
smtp_from TEXT,
status TEXT NOT NULL,
error_message TEXT,
sent_at TEXT,
created_at TEXT NOT NULL,
FOREIGN KEY(draft_id) REFERENCES drafts(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_sent_messages_draft ON sent_messages(draft_id);
CREATE INDEX IF NOT EXISTS idx_sent_messages_status ON sent_messages(status);
`