feat: auth backend (#111)
* refactor(types): extract inline anonymous types into named classes
Problem: several functions used inline `{...}` table types in their
`@param` and `@return` annotations, making them hard to read and
impossible to reference from other modules.
Solution: extract each into a named `---@class`: `pending.Metadata`,
`pending.TaskFields`, `pending.CompletionItem`, `pending.SystemResult`,
and `pending.OAuthClientOpts`.
* refactor(sync): extract shared utilities into `sync/util.lua`
Problem: sync epilogue code (`s:save()`, `_recompute_counts()`,
`buffer.render()`) and `fmt_counts` were duplicated across `gcal.lua`
and `gtasks.lua`. The concurrency guard lived in `oauth.lua`, coupling
non-OAuth backends to the OAuth module.
Solution: create `sync/util.lua` with `async`, `system`, `with_guard`,
`finish`, and `fmt_counts`. Delegate from `oauth.lua` and replace
duplicated code in both backends. Add per-backend `auth()` and
`auth_complete()` methods to `gcal.lua` and `gtasks.lua`.
* feat(sync): auto-discover backends, per-backend auth, S3 backend
Problem: sync backends were hardcoded in `SYNC_BACKENDS` list in
`init.lua`, auth routed directly through `oauth.google_client`, and
adding a non-OAuth backend required editing multiple files.
Solution: replace hardcoded list with `discover_backends()` that globs
`lua/pending/sync/*.lua` at runtime. Rewrite `M.auth()` to dispatch
to per-backend `auth()` methods with `vim.ui.select` fallback. Add
`lua/pending/sync/s3.lua` with push/pull/sync via AWS CLI, per-task
merge by `_s3_sync_id` (UUID), and `pending.S3Config` type.
This commit is contained in:
parent
ac02526cf1
commit
fe4c1d0e31
13 changed files with 1173 additions and 107 deletions
111
doc/pending.txt
111
doc/pending.txt
|
|
@ -66,8 +66,9 @@ CONTENTS *pending-contents*
|
|||
18. Google Calendar .......................................... |pending-gcal|
|
||||
19. Google Tasks ............................................ |pending-gtasks|
|
||||
20. Google Authentication ......................... |pending-google-auth|
|
||||
21. Data Format .............................................. |pending-data|
|
||||
22. Health Check ........................................... |pending-health|
|
||||
21. S3 Sync ................................................... |pending-s3|
|
||||
22. Data Format .............................................. |pending-data|
|
||||
23. Health Check ........................................... |pending-health|
|
||||
|
||||
==============================================================================
|
||||
REQUIREMENTS *pending-requirements*
|
||||
|
|
@ -1065,16 +1066,23 @@ Open tasks in a new tab on startup: >lua
|
|||
==============================================================================
|
||||
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
|
||||
Sync backends are Lua modules under `lua/pending/sync/<name>.lua`. Backends
|
||||
are auto-discovered at runtime — any module that exports a `name` field is
|
||||
registered automatically. No hardcoded list or manual registration step is
|
||||
required. Adding a backend is as simple as creating a new file.
|
||||
|
||||
Each backend is exposed as a top-level `:Pending` subcommand: >vim
|
||||
:Pending gtasks {action}
|
||||
:Pending gcal {action}
|
||||
:Pending s3 {action}
|
||||
<
|
||||
|
||||
Each module returns a table conforming to the backend interface: >lua
|
||||
|
||||
---@class pending.SyncBackend
|
||||
---@field name string
|
||||
---@field auth? fun(args?: string): nil
|
||||
---@field auth_complete? fun(arg_lead: string): string[]
|
||||
---@field push? fun(): nil
|
||||
---@field pull? fun(): nil
|
||||
---@field sync? fun(): nil
|
||||
|
|
@ -1085,14 +1093,28 @@ Required fields: ~
|
|||
{name} Backend identifier (matches the filename).
|
||||
|
||||
Optional fields: ~
|
||||
{auth} Per-backend authentication. Called by `:Pending auth <name>`.
|
||||
Receives an optional sub-action string (e.g. `"clear"`).
|
||||
{auth_complete} Returns valid sub-action completions for tab completion
|
||||
(e.g. `{ "clear", "reset" }`).
|
||||
{push} Push-only action. Called by `:Pending <name> push`.
|
||||
{pull} Pull-only action. Called by `:Pending <name> pull`.
|
||||
{sync} Main sync action. Called by `:Pending <name> sync`.
|
||||
{health} Called by `:checkhealth pending` to report backend-specific
|
||||
diagnostics (e.g. checking for external tools).
|
||||
|
||||
Note: authorization is not a per-backend action. Use `:Pending auth` to
|
||||
authenticate all Google backends at once. See |pending-google-auth|.
|
||||
Modules without a `name` field (e.g. `oauth.lua`, `util.lua`) are ignored
|
||||
by discovery and do not appear as backends.
|
||||
|
||||
Shared utilities for backend authors are provided by `sync/util.lua`:
|
||||
`util.async(fn)` Coroutine wrapper for async operations.
|
||||
`util.system(args)` Coroutine-aware `vim.system` wrapper.
|
||||
`util.with_guard(name, fn)` Concurrency guard — prevents overlapping
|
||||
sync operations. Clears on return or error.
|
||||
`util.finish(s)` Persist store, recompute counts, re-render
|
||||
the buffer. Typical sync epilogue.
|
||||
`util.fmt_counts(parts)` Format `{ {n, label}, ... }` into a
|
||||
human-readable summary string.
|
||||
|
||||
Backend-specific configuration goes under `sync.<name>` in |pending-config|.
|
||||
|
||||
|
|
@ -1233,11 +1255,20 @@ scopes (`tasks` + `calendar`). One authorization flow covers both services
|
|||
and produces one token file.
|
||||
|
||||
:Pending auth ~
|
||||
Prompts with |vim.ui.select| offering three options: `gtasks`, `gcal`, and
|
||||
`both`. All three options run the identical combined OAuth flow — the choice
|
||||
is informational only. If no real credentials are configured (i.e. bundled
|
||||
placeholders are in use), the setup wizard runs first to collect a client ID
|
||||
and client secret before opening the browser.
|
||||
`:Pending auth` dispatches to per-backend `auth()` methods. When called
|
||||
without arguments, if multiple backends have auth methods, a
|
||||
|vim.ui.select| prompt lets you choose. With an explicit backend name,
|
||||
the call goes directly: >vim
|
||||
:Pending auth gcal
|
||||
:Pending auth gtasks
|
||||
:Pending auth gcal clear
|
||||
:Pending auth gtasks reset
|
||||
<
|
||||
|
||||
Sub-actions are backend-specific. Google backends support `clear` (remove
|
||||
tokens) and `reset` (remove tokens and credentials). If no real credentials
|
||||
are configured (i.e. bundled placeholders are in use), the setup wizard runs
|
||||
first to collect a client ID and client secret before opening the browser.
|
||||
|
||||
OAuth flow: ~
|
||||
A PKCE (Proof Key for Code Exchange) flow is used:
|
||||
|
|
@ -1261,6 +1292,64 @@ Credentials are resolved in order for the `google` config key:
|
|||
|
||||
The `installed` wrapper format from the Google Cloud Console is accepted.
|
||||
|
||||
==============================================================================
|
||||
S3 SYNC *pending-s3*
|
||||
|
||||
pending.nvim can sync the task store to an S3 bucket. This enables
|
||||
whole-store synchronization between machines via the AWS CLI.
|
||||
|
||||
Configuration: >lua
|
||||
vim.g.pending = {
|
||||
sync = {
|
||||
s3 = {
|
||||
bucket = 'my-tasks-bucket',
|
||||
key = 'pending.json', -- optional, default "pending.json"
|
||||
profile = 'personal', -- optional AWS CLI profile
|
||||
region = 'us-east-1', -- optional region override
|
||||
},
|
||||
},
|
||||
}
|
||||
<
|
||||
|
||||
*pending.S3Config*
|
||||
Fields: ~
|
||||
{bucket} (string, required)
|
||||
S3 bucket name.
|
||||
|
||||
{key} (string, optional, default `"pending.json"`)
|
||||
S3 object key (path within the bucket).
|
||||
|
||||
{profile} (string, optional)
|
||||
AWS CLI profile name. Maps to `--profile`.
|
||||
|
||||
{region} (string, optional)
|
||||
AWS region override. Maps to `--region`.
|
||||
|
||||
Credential resolution: ~
|
||||
Delegates entirely to the AWS CLI credential chain (environment variables,
|
||||
~/.aws/credentials, IAM roles, SSO, etc.). No credentials are stored by
|
||||
pending.nvim.
|
||||
|
||||
Auth flow: ~
|
||||
`:Pending auth s3` runs `aws sts get-caller-identity` to verify credentials.
|
||||
If the profile uses SSO and the session has expired, it automatically runs
|
||||
`aws sso login`. Sub-action `profile` prompts for a profile name.
|
||||
|
||||
`:Pending s3 push` behavior: ~
|
||||
Assigns a `_s3_sync_id` (UUID) to each task that lacks one, serializes the
|
||||
store to a temp file, and uploads it to S3 via `aws s3 cp`.
|
||||
|
||||
`:Pending s3 pull` behavior: ~
|
||||
Downloads the remote store from S3, then merges per-task by `_s3_sync_id`:
|
||||
- Remote task with a matching local task: the version with the newer
|
||||
`modified` timestamp wins.
|
||||
- Remote task with no local match: added to the local store.
|
||||
- Local tasks not present in the remote: kept (local-only tasks are never
|
||||
deleted by pull).
|
||||
|
||||
`:Pending s3 sync` behavior: ~
|
||||
Pulls first (merge), then pushes the merged result.
|
||||
|
||||
==============================================================================
|
||||
DATA FORMAT *pending-data*
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue