feat(PROJ-45): IMAP Per-Folder UID-Tracking + UIDVALIDITY-Check
- FolderState: GetFolderState, UpsertFolderState, ListFolderStates, DecideResync - syncFolder nutzt per-folder UID-Tracking statt globalem highest_uid - UIDVALIDITY-Check loest automatisch Full-Resync aus - imap_folder_state Tabelle in initSchema (CREATE TABLE IF NOT EXISTS) - SetAuditLogger in main.go verdrahtet Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,20 @@ ALTER TABLE imap_accounts ADD COLUMN IF NOT EXISTS sync_error_msg TEXT NOT NULL
|
||||
ALTER TABLE imap_accounts ADD COLUMN IF NOT EXISTS tenant_id INTEGER REFERENCES tenants(id);
|
||||
`
|
||||
|
||||
// folderStateSQL creates the per-folder UID-tracking table introduced by PROJ-45.
|
||||
// Replaces the previous account-global last_uid tracking which silently skipped
|
||||
// folders with lower UIDs than INBOX and ignored UIDVALIDITY changes.
|
||||
const folderStateSQL = `
|
||||
CREATE TABLE IF NOT EXISTS imap_folder_state (
|
||||
account_id BIGINT NOT NULL REFERENCES imap_accounts(id) ON DELETE CASCADE,
|
||||
folder TEXT NOT NULL,
|
||||
last_uid BIGINT NOT NULL DEFAULT 0,
|
||||
uid_validity BIGINT NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (account_id, folder)
|
||||
);
|
||||
`
|
||||
|
||||
// New creates a new Store, connects to PostgreSQL, and runs the migration.
|
||||
func New(dsn, secret string) (*Store, error) {
|
||||
pool, err := pgxpool.New(context.Background(), dsn)
|
||||
@@ -103,6 +117,11 @@ func New(dsn, secret string) (*Store, error) {
|
||||
return nil, fmt.Errorf("imap store: migrate alter: %w", err)
|
||||
}
|
||||
|
||||
if _, err := pool.Exec(context.Background(), folderStateSQL); err != nil {
|
||||
pool.Close()
|
||||
return nil, fmt.Errorf("imap store: migrate folder state: %w", err)
|
||||
}
|
||||
|
||||
key := sha256.Sum256([]byte(secret))
|
||||
return &Store{pool: pool, encKey: key}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user