feat: add hard delete flag and opal clean command

Add --hard flag to `opal delete` for permanent removal and a new
`opal clean` command to bulk-purge soft-deleted tasks with optional
--older duration filter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 22:30:50 +01:00
parent 08123aa3c5
commit 10421b0ec6
4 changed files with 153 additions and 11 deletions
+42
View File
@@ -736,6 +736,48 @@ func (t *Task) IsRecurringInstance() bool {
return t.ParentUUID != nil
}
// GetDeletedTasks retrieves soft-deleted tasks, optionally filtered by age.
// If olderThan is non-nil, only returns tasks deleted more than that duration ago.
func GetDeletedTasks(olderThan *time.Duration) ([]*Task, error) {
db := GetDB()
if db == nil {
return nil, fmt.Errorf("database not initialized")
}
query := `
SELECT id, uuid, status, description, project, priority,
created, modified, start, end, due, scheduled, wait, until_date,
recurrence_duration, parent_uuid, annotations
FROM tasks
WHERE status = ?`
args := []interface{}{byte(StatusDeleted)}
if olderThan != nil {
cutoff := timeNow().Add(-*olderThan).Unix()
query += ` AND end < ?`
args = append(args, cutoff)
}
query += ` ORDER BY end ASC`
rows, err := db.Query(query, args...)
if err != nil {
return nil, fmt.Errorf("failed to query deleted tasks: %w", err)
}
defer rows.Close()
var tasks []*Task
for rows.Next() {
task, err := scanTask(rows)
if err != nil {
return nil, fmt.Errorf("failed to scan task: %w", err)
}
tasks = append(tasks, task)
}
return tasks, nil
}
// PopulateUrgency computes and sets the Urgency field on the given tasks.
func PopulateUrgency(tasks ...*Task) {
cfg, _ := GetConfig()