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
34a68db6d0
commit
d12838abbf
13 changed files with 1173 additions and 107 deletions
|
|
@ -933,13 +933,30 @@ function M.add(text)
|
|||
log.info('Task added: ' .. description)
|
||||
end
|
||||
|
||||
---@type string[]
|
||||
local SYNC_BACKENDS = { 'gcal', 'gtasks' }
|
||||
---@type string[]?
|
||||
local _sync_backends = nil
|
||||
|
||||
---@type table<string, true>
|
||||
local SYNC_BACKEND_SET = {}
|
||||
for _, b in ipairs(SYNC_BACKENDS) do
|
||||
SYNC_BACKEND_SET[b] = true
|
||||
---@type table<string, true>?
|
||||
local _sync_backend_set = nil
|
||||
|
||||
---@return string[], table<string, true>
|
||||
local function discover_backends()
|
||||
if _sync_backends then
|
||||
return _sync_backends, _sync_backend_set --[[@as table<string, true>]]
|
||||
end
|
||||
_sync_backends = {}
|
||||
_sync_backend_set = {}
|
||||
local paths = vim.fn.globpath(vim.o.runtimepath, 'lua/pending/sync/*.lua', false, true)
|
||||
for _, path in ipairs(paths) do
|
||||
local name = vim.fn.fnamemodify(path, ':t:r')
|
||||
local ok, mod = pcall(require, 'pending.sync.' .. name)
|
||||
if ok and type(mod) == 'table' and mod.name then
|
||||
table.insert(_sync_backends, mod.name)
|
||||
_sync_backend_set[mod.name] = true
|
||||
end
|
||||
end
|
||||
table.sort(_sync_backends)
|
||||
return _sync_backends, _sync_backend_set
|
||||
end
|
||||
|
||||
---@param backend_name string
|
||||
|
|
@ -954,7 +971,13 @@ local function run_sync(backend_name, action)
|
|||
if not action or action == '' then
|
||||
local actions = {}
|
||||
for k, v in pairs(backend) do
|
||||
if type(v) == 'function' and k:sub(1, 1) ~= '_' and k ~= 'health' then
|
||||
if
|
||||
type(v) == 'function'
|
||||
and k:sub(1, 1) ~= '_'
|
||||
and k ~= 'health'
|
||||
and k ~= 'auth'
|
||||
and k ~= 'auth_complete'
|
||||
then
|
||||
table.insert(actions, k)
|
||||
end
|
||||
end
|
||||
|
|
@ -1246,29 +1269,55 @@ end
|
|||
---@param args? string
|
||||
---@return nil
|
||||
function M.auth(args)
|
||||
local oauth = require('pending.sync.oauth')
|
||||
local parts = {}
|
||||
for w in (args or ''):gmatch('%S+') do
|
||||
table.insert(parts, w)
|
||||
end
|
||||
local action = parts[#parts]
|
||||
if action == parts[1] and (action == 'gtasks' or action == 'gcal') then
|
||||
action = nil
|
||||
|
||||
local backend_name = parts[1]
|
||||
local sub_action = parts[2]
|
||||
|
||||
local backends_list = discover_backends()
|
||||
local auth_backends = {}
|
||||
for _, name in ipairs(backends_list) do
|
||||
local ok, mod = pcall(require, 'pending.sync.' .. name)
|
||||
if ok and type(mod.auth) == 'function' then
|
||||
table.insert(auth_backends, { name = name, mod = mod })
|
||||
end
|
||||
end
|
||||
|
||||
if action == 'clear' then
|
||||
oauth.google_client:clear_tokens()
|
||||
log.info('OAuth tokens cleared — run :Pending auth to re-authenticate.')
|
||||
elseif action == 'reset' then
|
||||
oauth.google_client:_wipe()
|
||||
log.info('OAuth tokens and credentials cleared — run :Pending auth to set up from scratch.')
|
||||
else
|
||||
local creds = oauth.google_client:resolve_credentials()
|
||||
if creds.client_id == oauth.BUNDLED_CLIENT_ID then
|
||||
oauth.google_client:setup()
|
||||
else
|
||||
oauth.google_client:auth()
|
||||
if backend_name then
|
||||
local found = false
|
||||
for _, b in ipairs(auth_backends) do
|
||||
if b.name == backend_name then
|
||||
b.mod.auth(sub_action)
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
log.error('No auth method for backend: ' .. backend_name)
|
||||
end
|
||||
elseif #auth_backends == 1 then
|
||||
auth_backends[1].mod.auth()
|
||||
elseif #auth_backends > 1 then
|
||||
local names = {}
|
||||
for _, b in ipairs(auth_backends) do
|
||||
table.insert(names, b.name)
|
||||
end
|
||||
vim.ui.select(names, { prompt = 'Authenticate backend: ' }, function(choice)
|
||||
if not choice then
|
||||
return
|
||||
end
|
||||
for _, b in ipairs(auth_backends) do
|
||||
if b.name == choice then
|
||||
b.mod.auth()
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
else
|
||||
log.warn('No sync backends with auth support found.')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1289,7 +1338,7 @@ function M.command(args)
|
|||
M.edit(id_str, edit_rest)
|
||||
elseif cmd == 'auth' then
|
||||
M.auth(rest)
|
||||
elseif SYNC_BACKEND_SET[cmd] then
|
||||
elseif select(2, discover_backends())[cmd] then
|
||||
local action = rest:match('^(%S+)')
|
||||
run_sync(cmd, action)
|
||||
elseif cmd == 'archive' then
|
||||
|
|
@ -1307,12 +1356,13 @@ end
|
|||
|
||||
---@return string[]
|
||||
function M.sync_backends()
|
||||
return SYNC_BACKENDS
|
||||
return (discover_backends())
|
||||
end
|
||||
|
||||
---@return table<string, true>
|
||||
function M.sync_backend_set()
|
||||
return SYNC_BACKEND_SET
|
||||
local _, set = discover_backends()
|
||||
return set
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue