feat(sync): add opt-in remote deletion for gcal and gtasks (#85)

Problem: push/sync permanently deleted remote Google Calendar events and
Google Tasks entries whenever a local task was marked deleted, done, or
de-due'd. There was no opt-out, so a misfire could silently cause
irreversible data loss on the remote side.

Solution: add a `remote_delete` boolean to the config (default `false`).
A unified flag at `sync.remote_delete` sets the base; per-backend
overrides at `sync.gcal.remote_delete` / `sync.gtasks.remote_delete`
take precedence when non-nil. When disabled, `_extra` remote IDs are
cleared silently (unlinking) so stale IDs don't accumulate.
This commit is contained in:
Barrett Ruth 2026-03-06 13:12:53 -05:00
parent 25ad5a6d88
commit 9de53f2bb3
3 changed files with 78 additions and 26 deletions

View file

@ -172,6 +172,29 @@ local function parse_notes(notes)
return priority, recur, recur_mode
end
---@return boolean
local function allow_remote_delete()
local cfg = config.get()
local sync = cfg.sync or {}
local per = (sync.gtasks or {}) --[[@as pending.GtasksConfig]]
if per.remote_delete ~= nil then
return per.remote_delete == true
end
return sync.remote_delete == true
end
---@param task pending.Task
---@param now_ts string
local function unlink_remote(task, now_ts)
task._extra['_gtasks_task_id'] = nil
task._extra['_gtasks_list_id'] = nil
task._extra['_gtasks_synced_at'] = nil
if next(task._extra) == nil then
task._extra = nil
end
task.modified = now_ts
end
---@param task pending.Task
---@return table
local function task_to_gtask(task)
@ -240,21 +263,20 @@ local function push_pass(access_token, tasklists, s, now_ts, by_gtasks_id)
local list_id = extra['_gtasks_list_id'] --[[@as string?]]
if task.status == 'deleted' and gtid and list_id then
local err = delete_gtask(access_token, list_id, gtid)
if err then
log.warn('gtasks delete failed: ' .. err)
failed = failed + 1
if allow_remote_delete() then
local err = delete_gtask(access_token, list_id, gtid)
if err then
log.warn('gtasks delete failed: ' .. err)
failed = failed + 1
else
unlink_remote(task, now_ts)
deleted = deleted + 1
end
else
if not task._extra then
task._extra = {}
end
task._extra['_gtasks_task_id'] = nil
task._extra['_gtasks_list_id'] = nil
task._extra['_gtasks_synced_at'] = nil
if next(task._extra) == nil then
task._extra = nil
end
task.modified = now_ts
log.debug(
'gtasks: remote delete skipped (remote_delete disabled) — unlinked task #' .. task.id
)
unlink_remote(task, now_ts)
deleted = deleted + 1
end
elseif task.status ~= 'deleted' then