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,3 +159,51 @@ func (s *Store) SaveRules(ctx context.Context, text string) (int, error) {
|
||||
}
|
||||
return next, nil
|
||||
}
|
||||
|
||||
// RuleVersion is one saved rules revision, for the version list in the editor
|
||||
// (FR-11).
|
||||
type RuleVersion struct {
|
||||
ID int64
|
||||
Version int
|
||||
Text string
|
||||
CreatedAt string
|
||||
}
|
||||
|
||||
// RulesVersions returns saved rules revisions, newest first.
|
||||
func (s *Store) RulesVersions(ctx context.Context, limit int) ([]RuleVersion, error) {
|
||||
if limit < 1 {
|
||||
limit = 20
|
||||
}
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
`SELECT id, version, text, created_at FROM rules ORDER BY version DESC LIMIT ?`, limit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list rules versions: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []RuleVersion
|
||||
for rows.Next() {
|
||||
var v RuleVersion
|
||||
if err := rows.Scan(&v.ID, &v.Version, &v.Text, &v.CreatedAt); err != nil {
|
||||
return nil, fmt.Errorf("scan rules version: %w", err)
|
||||
}
|
||||
out = append(out, v)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("list rules versions: %w", err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// RuleByVersion returns the text of one saved rules revision. ok is false when
|
||||
// the version does not exist.
|
||||
func (s *Store) RuleByVersion(ctx context.Context, version int) (string, bool, error) {
|
||||
var text string
|
||||
err := s.db.QueryRowContext(ctx, `SELECT text FROM rules WHERE version = ?`, version).Scan(&text)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return "", false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("read rules version %d: %w", version, err)
|
||||
}
|
||||
return text, true, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user