fix: harden sync backends and fix edit recompute (#66)
* refactor(oauth): async coroutine support, pure-Lua PKCE, server hardening Problem: OAuth module shelled out to openssl for PKCE, used blocking `vim.system():wait()`, had a weak `os.time()` PRNG seed, and the TCP callback server leaked on read errors with no timeout. Solution: Add `M.system()` coroutine wrapper and `M.async()` helper, replace openssl with `vim.fn.sha256` + `vim.base64.encode`, seed from `vim.uv.hrtime()`, add `close_server()` guard with 120s timeout, and close the server on read errors. * fix(gtasks): async operations, error notifications, buffer refresh Problem: Sync operations blocked the editor, `push_pass` silently dropped delete/update/create API errors, and the buffer was not re-rendered after push/pull/sync. Solution: Wrap `push`, `pull`, `sync` in `oauth.async()`, add `vim.notify` for all `push_pass` failure paths, and re-render the pending buffer after each operation. * fix(init): edit recompute, filter predicates, sync action listing Problem: `M.edit()` skipped `_recompute_counts()` after saving, `compute_hidden_ids` lacked `done`/`pending` predicates, and `run_sync` defaulted to `sync` instead of listing available actions. Solution: Replace `s:save()` with `_save_and_notify()` in `M.edit()`, add `done` and `pending` filter predicates, and list backend actions when no action is specified. * 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. * refactor: formatting fixes, config cleanup, health simplification Problem: Formatter disagreements in `init.lua` and `gtasks.lua`, stale `calendar` field in gcal config, and redundant health checks for data directory existence. Solution: Apply stylua formatting, remove `calendar` field from `pending.GcalConfig`, drop data-dir and no-file health messages, add `done`/`pending` to filter tab-completion candidates. * docs: update vimdoc for sync refactor, remove demo scripts Problem: Docs still referenced openssl dependency, defaulting to `sync` action, and the `calendar` config field. Demo scripts used the old singleton `store` API. Solution: Update vimdoc and README to reflect explicit actions, per- category calendars, and pure-Lua PKCE. Remove stale demo scripts and update sync specs to match new behavior. * fix(types): correct LuaLS annotations in oauth and gcal
This commit is contained in:
parent
e0e3af6787
commit
b7ce1c05ec
13 changed files with 319 additions and 291 deletions
|
|
@ -73,7 +73,7 @@ REQUIREMENTS *pending-requirements*
|
|||
|
||||
- Neovim 0.10+
|
||||
- No external dependencies for local use
|
||||
- `curl` and `openssl` are required for the `gcal` and `gtasks` sync backends
|
||||
- `curl` is required for the `gcal` and `gtasks` sync backends
|
||||
|
||||
==============================================================================
|
||||
INSTALL *pending-install*
|
||||
|
|
@ -149,17 +149,17 @@ COMMANDS *pending-commands*
|
|||
Open the list with |:copen| to navigate to each task's category.
|
||||
|
||||
*:Pending-gtasks*
|
||||
:Pending gtasks [{action}]
|
||||
Run a Google Tasks sync action. {action} defaults to `sync` when omitted.
|
||||
:Pending gtasks {action}
|
||||
Run a Google Tasks action. An explicit action is required.
|
||||
|
||||
Actions: ~
|
||||
`sync` Push local changes then pull remote changes (default).
|
||||
`sync` Push local changes then pull remote changes.
|
||||
`push` Push local changes to Google Tasks only.
|
||||
`pull` Pull remote changes from Google Tasks only.
|
||||
`auth` Run the OAuth authorization flow.
|
||||
|
||||
Examples: >vim
|
||||
:Pending gtasks " push then pull (default)
|
||||
:Pending gtasks sync " push then pull
|
||||
:Pending gtasks push " push local → Google Tasks
|
||||
:Pending gtasks pull " pull Google Tasks → local
|
||||
:Pending gtasks auth " authorize
|
||||
|
|
@ -169,16 +169,15 @@ COMMANDS *pending-commands*
|
|||
See |pending-gtasks| for full details.
|
||||
|
||||
*:Pending-gcal*
|
||||
:Pending gcal [{action}]
|
||||
Run a Google Calendar sync action. {action} defaults to `sync` when
|
||||
omitted.
|
||||
:Pending gcal {action}
|
||||
Run a Google Calendar action. An explicit action is required.
|
||||
|
||||
Actions: ~
|
||||
`sync` Push tasks with due dates to Google Calendar (default).
|
||||
`push` Push tasks with due dates to Google Calendar.
|
||||
`auth` Run the OAuth authorization flow.
|
||||
|
||||
Examples: >vim
|
||||
:Pending gcal " push to Google Calendar (default)
|
||||
:Pending gcal push " push to Google Calendar
|
||||
:Pending gcal auth " authorize
|
||||
<
|
||||
|
||||
|
|
@ -606,9 +605,7 @@ loads: >lua
|
|||
prev_task = '[t',
|
||||
},
|
||||
sync = {
|
||||
gcal = {
|
||||
calendar = 'Pendings',
|
||||
},
|
||||
gcal = {},
|
||||
gtasks = {},
|
||||
},
|
||||
}
|
||||
|
|
@ -893,8 +890,8 @@ SYNC BACKENDS *pending-sync-backend*
|
|||
|
||||
Sync backends are Lua modules under `lua/pending/sync/<name>.lua`. Each
|
||||
backend is exposed as a top-level `:Pending` subcommand: >vim
|
||||
:Pending gtasks [action]
|
||||
:Pending gcal [action]
|
||||
:Pending gtasks {action}
|
||||
:Pending gcal {action}
|
||||
<
|
||||
|
||||
Each module returns a table conforming to the backend interface: >lua
|
||||
|
|
@ -902,9 +899,9 @@ Each module returns a table conforming to the backend interface: >lua
|
|||
---@class pending.SyncBackend
|
||||
---@field name string
|
||||
---@field auth fun(): nil
|
||||
---@field sync fun(): nil
|
||||
---@field push? fun(): nil
|
||||
---@field pull? fun(): nil
|
||||
---@field sync? fun(): nil
|
||||
---@field health? fun(): nil
|
||||
<
|
||||
|
||||
|
|
@ -924,16 +921,15 @@ Backend-specific configuration goes under `sync.<name>` in |pending-config|.
|
|||
==============================================================================
|
||||
GOOGLE CALENDAR *pending-gcal*
|
||||
|
||||
pending.nvim can push tasks with due dates to a dedicated Google Calendar as
|
||||
all-day events. This is a one-way push; changes made in Google Calendar are
|
||||
not pulled back into pending.nvim.
|
||||
pending.nvim can push tasks with due dates to Google Calendar as all-day
|
||||
events. Each pending.nvim category maps to a Google Calendar of the same
|
||||
name. Calendars are created automatically on first push. This is a one-way
|
||||
push; changes made in Google Calendar are not pulled back.
|
||||
|
||||
Configuration: >lua
|
||||
vim.g.pending = {
|
||||
sync = {
|
||||
gcal = {
|
||||
calendar = 'Pendings',
|
||||
},
|
||||
gcal = {},
|
||||
},
|
||||
}
|
||||
<
|
||||
|
|
@ -943,11 +939,6 @@ used by default. Run `:Pending gcal auth` and the browser opens immediately.
|
|||
|
||||
*pending.GcalConfig*
|
||||
Fields: ~
|
||||
{calendar} (string, default: 'Pendings')
|
||||
Name of the Google Calendar to sync to. If a calendar
|
||||
with this name does not exist it is created
|
||||
automatically on the first sync.
|
||||
|
||||
{client_id} (string, optional)
|
||||
OAuth client ID. When set together with
|
||||
{client_secret}, these take priority over the
|
||||
|
|
@ -973,26 +964,24 @@ OAuth flow: ~
|
|||
On the first `:Pending gcal` call the plugin detects that no refresh token
|
||||
exists and opens the Google authorization URL in the browser using
|
||||
|vim.ui.open()|. A temporary local HTTP server listens on port 18392 for the
|
||||
OAuth redirect. The PKCE (Proof Key for Code Exchange) flow is used —
|
||||
`openssl` generates the code challenge. After the user grants consent, the
|
||||
OAuth redirect. The PKCE (Proof Key for Code Exchange) flow is used. After
|
||||
the user grants consent, the
|
||||
authorization code is exchanged for tokens and the refresh token is stored at
|
||||
`stdpath('data')/pending/gcal_tokens.json` with mode `600`. Subsequent syncs
|
||||
use the stored refresh token and refresh the access token automatically when
|
||||
it is about to expire.
|
||||
|
||||
`:Pending gcal` behavior: ~
|
||||
`:Pending gcal push` behavior: ~
|
||||
For each task in the store:
|
||||
- A pending task with a due date and no existing event: a new all-day event is
|
||||
created and the event ID is stored in the task's `_extra` table.
|
||||
created in the calendar matching the task's category. The event ID and
|
||||
calendar ID are stored in the task's `_extra` table.
|
||||
- A pending task with a due date and an existing event: the event summary and
|
||||
date are updated in place.
|
||||
- A done or deleted task with an existing event: the event is deleted.
|
||||
- A pending task with no due date that had an existing event: the event is
|
||||
deleted.
|
||||
|
||||
A summary notification is shown after sync: `created: N, updated: N,
|
||||
deleted: N`.
|
||||
|
||||
==============================================================================
|
||||
GOOGLE TASKS *pending-gtasks*
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue