refactor(gcal): per-category calendars, async push, error notifications
Problem: gcal used a single hardcoded calendar name, ran synchronously blocking the editor, and silently dropped some API errors. Solution: Fetch all calendars and map categories to calendars (creating on demand), wrap push in `oauth.async()`, notify on individual API failures, track `_gcal_calendar_id` in `_extra`, and remove the `$` anchor from `next_day` pattern.
This commit is contained in:
parent
ca61db7127
commit
765d7fa0b5
1 changed files with 108 additions and 72 deletions
|
|
@ -15,13 +15,10 @@ local client = oauth.new({
|
||||||
config_key = 'gcal',
|
config_key = 'gcal',
|
||||||
})
|
})
|
||||||
|
|
||||||
---@return string? calendar_id
|
---@param access_token string
|
||||||
|
---@return table<string, string>? name_to_id
|
||||||
---@return string? err
|
---@return string? err
|
||||||
local function find_or_create_calendar(access_token)
|
local function get_all_calendars(access_token)
|
||||||
local cfg = config.get()
|
|
||||||
local gc = (cfg.sync and cfg.sync.gcal) or {}
|
|
||||||
local cal_name = gc.calendar or 'Pendings'
|
|
||||||
|
|
||||||
local data, err = oauth.curl_request(
|
local data, err = oauth.curl_request(
|
||||||
'GET',
|
'GET',
|
||||||
BASE_URL .. '/users/me/calendarList',
|
BASE_URL .. '/users/me/calendarList',
|
||||||
|
|
@ -30,27 +27,41 @@ local function find_or_create_calendar(access_token)
|
||||||
if err then
|
if err then
|
||||||
return nil, err
|
return nil, err
|
||||||
end
|
end
|
||||||
|
local result = {}
|
||||||
for _, item in ipairs(data and data.items or {}) do
|
for _, item in ipairs(data and data.items or {}) do
|
||||||
if item.summary == cal_name then
|
if item.summary then
|
||||||
return item.id, nil
|
result[item.summary] = item.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
return result, nil
|
||||||
|
end
|
||||||
|
|
||||||
local body = vim.json.encode({ summary = cal_name })
|
---@param access_token string
|
||||||
local created, create_err =
|
---@param name string
|
||||||
oauth.curl_request('POST', BASE_URL .. '/calendars', oauth.auth_headers(access_token), body)
|
---@param existing table<string, string>
|
||||||
if create_err then
|
---@return string? calendar_id
|
||||||
return nil, create_err
|
---@return string? err
|
||||||
|
local function find_or_create_calendar(access_token, name, existing)
|
||||||
|
if existing[name] then
|
||||||
|
return existing[name], nil
|
||||||
end
|
end
|
||||||
|
local body = vim.json.encode({ summary = name })
|
||||||
return created and created.id, nil
|
local created, err =
|
||||||
|
oauth.curl_request('POST', BASE_URL .. '/calendars', oauth.auth_headers(access_token), body)
|
||||||
|
if err then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
local id = created and created.id
|
||||||
|
if id then
|
||||||
|
existing[name] = id
|
||||||
|
end
|
||||||
|
return id, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param date_str string
|
---@param date_str string
|
||||||
---@return string
|
---@return string
|
||||||
local function next_day(date_str)
|
local function next_day(date_str)
|
||||||
local y, m, d = date_str:match('^(%d%d%d%d)-(%d%d)-(%d%d)$')
|
local y, m, d = date_str:match('^(%d%d%d%d)-(%d%d)-(%d%d)')
|
||||||
local t = os.time({ year = tonumber(y) or 0, month = tonumber(m) or 0, day = tonumber(d) or 0 })
|
local t = os.time({ year = tonumber(y) or 0, month = tonumber(m) or 0, day = tonumber(d) or 0 })
|
||||||
+ 86400
|
+ 86400
|
||||||
return os.date('%Y-%m-%d', t) --[[@as string]]
|
return os.date('%Y-%m-%d', t) --[[@as string]]
|
||||||
|
|
@ -128,74 +139,99 @@ function M.auth()
|
||||||
client:auth()
|
client:auth()
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.sync()
|
function M.push()
|
||||||
local access_token = client:get_access_token()
|
oauth.async(function()
|
||||||
if not access_token then
|
local access_token = client:get_access_token()
|
||||||
return
|
if not access_token then
|
||||||
end
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local calendar_id, err = find_or_create_calendar(access_token)
|
local calendars, cal_err = get_all_calendars(access_token)
|
||||||
if err or not calendar_id then
|
if cal_err or not calendars then
|
||||||
vim.notify('pending.nvim: ' .. (err or 'calendar not found'), vim.log.levels.ERROR)
|
vim.notify('pending.nvim: ' .. (cal_err or 'failed to fetch calendars'), vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local tasks = require('pending').store():tasks()
|
local s = require('pending').store()
|
||||||
local created, updated, deleted = 0, 0, 0
|
local created, updated, deleted = 0, 0, 0
|
||||||
|
|
||||||
for _, task in ipairs(tasks) do
|
for _, task in ipairs(s:tasks()) do
|
||||||
local extra = task._extra or {}
|
local extra = task._extra or {}
|
||||||
local event_id = extra['_gcal_event_id'] --[[@as string?]]
|
local event_id = extra['_gcal_event_id'] --[[@as string?]]
|
||||||
|
local cal_id = extra['_gcal_calendar_id'] --[[@as string?]]
|
||||||
|
|
||||||
local should_delete = event_id ~= nil
|
local should_delete = event_id ~= nil
|
||||||
and (
|
and cal_id ~= nil
|
||||||
task.status == 'done'
|
and (
|
||||||
or task.status == 'deleted'
|
task.status == 'done'
|
||||||
or (task.status == 'pending' and not task.due)
|
or task.status == 'deleted'
|
||||||
)
|
or (task.status == 'pending' and not task.due)
|
||||||
|
)
|
||||||
|
|
||||||
if should_delete and event_id then
|
if should_delete then
|
||||||
local del_err = delete_event(access_token, calendar_id, event_id) --[[@as string]]
|
local del_err = delete_event(access_token, cal_id, event_id)
|
||||||
if not del_err then
|
if del_err then
|
||||||
extra['_gcal_event_id'] = nil
|
vim.notify('pending.nvim: gcal delete failed: ' .. del_err, vim.log.levels.WARN)
|
||||||
if next(extra) == nil then
|
|
||||||
task._extra = nil
|
|
||||||
else
|
else
|
||||||
task._extra = extra
|
extra['_gcal_event_id'] = nil
|
||||||
end
|
extra['_gcal_calendar_id'] = nil
|
||||||
task.modified = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]]
|
if next(extra) == nil then
|
||||||
deleted = deleted + 1
|
task._extra = nil
|
||||||
end
|
else
|
||||||
elseif task.status == 'pending' and task.due then
|
task._extra = extra
|
||||||
if event_id then
|
|
||||||
local upd_err = update_event(access_token, calendar_id, event_id, task)
|
|
||||||
if not upd_err then
|
|
||||||
updated = updated + 1
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local new_id, create_err = create_event(access_token, calendar_id, task)
|
|
||||||
if not create_err and new_id then
|
|
||||||
if not task._extra then
|
|
||||||
task._extra = {}
|
|
||||||
end
|
end
|
||||||
task._extra['_gcal_event_id'] = new_id
|
|
||||||
task.modified = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]]
|
task.modified = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]]
|
||||||
created = created + 1
|
deleted = deleted + 1
|
||||||
|
end
|
||||||
|
elseif task.status == 'pending' and task.due then
|
||||||
|
local cat = task.category or config.get().default_category
|
||||||
|
if event_id and cal_id then
|
||||||
|
local upd_err = update_event(access_token, cal_id, event_id, task)
|
||||||
|
if upd_err then
|
||||||
|
vim.notify('pending.nvim: gcal update failed: ' .. upd_err, vim.log.levels.WARN)
|
||||||
|
else
|
||||||
|
updated = updated + 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local lid, lid_err = find_or_create_calendar(access_token, cat, calendars)
|
||||||
|
if lid_err or not lid then
|
||||||
|
vim.notify(
|
||||||
|
'pending.nvim: gcal calendar failed: ' .. (lid_err or 'unknown'),
|
||||||
|
vim.log.levels.WARN
|
||||||
|
)
|
||||||
|
else
|
||||||
|
local new_id, create_err = create_event(access_token, lid, task)
|
||||||
|
if create_err then
|
||||||
|
vim.notify('pending.nvim: gcal create failed: ' .. create_err, vim.log.levels.WARN)
|
||||||
|
elseif new_id then
|
||||||
|
if not task._extra then
|
||||||
|
task._extra = {}
|
||||||
|
end
|
||||||
|
task._extra['_gcal_event_id'] = new_id
|
||||||
|
task._extra['_gcal_calendar_id'] = lid
|
||||||
|
task.modified = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]]
|
||||||
|
created = created + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
require('pending').store():save()
|
s:save()
|
||||||
require('pending')._recompute_counts()
|
require('pending')._recompute_counts()
|
||||||
vim.notify(
|
local buffer = require('pending.buffer')
|
||||||
string.format(
|
if buffer.bufnr() and vim.api.nvim_buf_is_valid(buffer.bufnr()) then
|
||||||
'pending.nvim: Synced to Google Calendar (created: %d, updated: %d, deleted: %d)',
|
buffer.render(buffer.bufnr())
|
||||||
created,
|
end
|
||||||
updated,
|
vim.notify(
|
||||||
deleted
|
string.format(
|
||||||
|
'pending.nvim: Google Calendar pushed — +%d ~%d -%d',
|
||||||
|
created,
|
||||||
|
updated,
|
||||||
|
deleted
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return nil
|
---@return nil
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue