Files
vikunja/pkg/models/task_delete_cron.go
kallegrens 82ee780161 feat(caldav): implement RFC 6578 sync-collection REPORT to sync task deletions
iOS Reminders (and other RFC 6578 clients) never learned about tasks
deleted on the Vikunja side: caldav-go answers unknown REPORT types
with 412, which makes iOS silently stop syncing, and deleted tasks left
no protocol-level trace a client could observe.

Intercept sync-collection REPORTs before caldav-go sees them and answer
per RFC 6578: tasks changed since the sync token as 200 entries and
deleted tasks as 404 entries. Deletion records come from task
soft-deletes, which are kept for 30 days before being purged.

Token edge cases: delta comparisons are inclusive because tokens have
second granularity (re-reported items keep their etag, so clients skip
the download), and tokens older than the soft-delete retention answer
403 + valid-sync-token to force a full resync instead of a delta that
would silently miss purged deletions.
2026-07-07 11:57:04 +00:00

86 lines
2.3 KiB
Go

// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package models
import (
"time"
"code.vikunja.io/api/pkg/cron"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"xorm.io/builder"
)
// TaskDeleteRetention is how long soft-deleted tasks are kept before permanent
// removal. Hard-coded like the user deletion grace period.
const TaskDeleteRetention = 30 * 24 * time.Hour
// RegisterTaskCleanupCron registers the cron job that permanently removes
// tasks which were soft-deleted more than TaskDeleteRetention ago.
func RegisterTaskCleanupCron() {
err := cron.Schedule("0 * * * *", func() {
deleteExpiredTasks(time.Now())
})
if err != nil {
log.Errorf("Could not register task cleanup cron: %s", err.Error())
}
}
func deleteExpiredTasks(now time.Time) {
s := db.NewSession()
tasks := []*Task{}
err := s.Unscoped().
Where(builder.And(
builder.NotNull{"deleted_at"},
builder.Lt{"deleted_at": now.Add(-TaskDeleteRetention)},
)).
Find(&tasks)
s.Close()
if err != nil {
log.Errorf("Could not get tasks scheduled for permanent deletion: %s", err)
return
}
if len(tasks) == 0 {
return
}
log.Debugf("Found %d tasks scheduled for permanent deletion", len(tasks))
for _, task := range tasks {
func() {
ts := db.NewSession()
defer ts.Close()
err = hardDeleteTask(ts, task)
if err != nil {
_ = ts.Rollback()
log.Errorf("Could not permanently delete task %d: %s", task.ID, err)
return
}
log.Debugf("Permanently deleted task %d", task.ID)
err = ts.Commit()
if err != nil {
log.Errorf("Could not commit transaction: %s", err)
}
}()
}
}