Phase 6: rules editor + unsubscribe detection (AUD-72)
- FR-11 rules editor: plain-text area at /rules, saved and versioned (SaveRules appends a version; restore re-saves an old version), parse errors surfaced, one-click re-classify. The reply model now reads the rules document too (FR-11). - FR-7 unsubscribe: parse and store List-Unsubscribe / List-Unsubscribe-Post (migration v6), detect email and web links (internal/unsubscribe), a domain-level ignore list, and an explicit record of what was marked. No code path contacts an unsubscribe link; acting on one needs Sam's approval. - Migration v6 is additive; unsubscribe gains url/updated_at. - Tests: detector, parser, store migration/records/candidates, rules editor, unsubscribe handlers, message-detail rendering. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -159,6 +159,25 @@ func (s *Store) applyMigrations(ctx context.Context) error {
|
||||
return fmt.Errorf("record schema version 5: %w", err)
|
||||
}
|
||||
}
|
||||
if version < 6 {
|
||||
// Phase 6 (FR-7): store the raw List-Unsubscribe headers so detection
|
||||
// runs without a refetch, and extend the unsubscribe tracker with the
|
||||
// link it recorded and an update time.
|
||||
for _, col := range []struct{ table, name, typ string }{
|
||||
{"messages", "list_unsubscribe", "TEXT"},
|
||||
{"messages", "list_unsubscribe_post", "TEXT"},
|
||||
{"unsubscribe", "url", "TEXT NOT NULL DEFAULT ''"},
|
||||
{"unsubscribe", "updated_at", "TEXT"},
|
||||
} {
|
||||
if err := s.ensureColumn(ctx, col.table, col.name, col.typ); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := s.db.ExecContext(ctx,
|
||||
`INSERT OR IGNORE INTO schema_migrations (version) VALUES (6)`); err != nil {
|
||||
return fmt.Errorf("record schema version 6: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user