fix(gcal): add LuaCATS annotations and resolve type errors
Problem: gcal.lua had ~10 LuaLS errors from untyped credential and token tables, string|osdate casts, and untyped _gcal_event_id field access. Solution: add pending.GcalCredentials and pending.GcalTokens class definitions, annotate all local functions with @param/@return, add --[[@as string]] casts on os.date returns, and fix _gcal_event_id access to use bracket notation with casts.
This commit is contained in:
parent
68dbea7d52
commit
fc45ca3fcd
1 changed files with 76 additions and 16 deletions
|
|
@ -8,20 +8,36 @@ local TOKEN_URL = 'https://oauth2.googleapis.com/token'
|
||||||
local AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
|
local AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
|
||||||
local SCOPE = 'https://www.googleapis.com/auth/calendar'
|
local SCOPE = 'https://www.googleapis.com/auth/calendar'
|
||||||
|
|
||||||
|
---@class pending.GcalCredentials
|
||||||
|
---@field client_id string
|
||||||
|
---@field client_secret string
|
||||||
|
---@field redirect_uris? string[]
|
||||||
|
|
||||||
|
---@class pending.GcalTokens
|
||||||
|
---@field access_token string
|
||||||
|
---@field refresh_token string
|
||||||
|
---@field expires_in? integer
|
||||||
|
---@field obtained_at? integer
|
||||||
|
|
||||||
|
---@return table<string, any>
|
||||||
local function gcal_config()
|
local function gcal_config()
|
||||||
local cfg = config.get()
|
local cfg = config.get()
|
||||||
return cfg.gcal or {}
|
return cfg.gcal or {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return string
|
||||||
local function token_path()
|
local function token_path()
|
||||||
return vim.fn.stdpath('data') .. '/pending/gcal_tokens.json'
|
return vim.fn.stdpath('data') .. '/pending/gcal_tokens.json'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return string
|
||||||
local function credentials_path()
|
local function credentials_path()
|
||||||
local gc = gcal_config()
|
local gc = gcal_config()
|
||||||
return gc.credentials_path or (vim.fn.stdpath('data') .. '/pending/gcal_credentials.json')
|
return gc.credentials_path or (vim.fn.stdpath('data') .. '/pending/gcal_credentials.json')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@return table?
|
||||||
local function load_json_file(path)
|
local function load_json_file(path)
|
||||||
local f = io.open(path, 'r')
|
local f = io.open(path, 'r')
|
||||||
if not f then
|
if not f then
|
||||||
|
|
@ -39,6 +55,9 @@ local function load_json_file(path)
|
||||||
return decoded
|
return decoded
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@param data table
|
||||||
|
---@return boolean
|
||||||
local function save_json_file(path, data)
|
local function save_json_file(path, data)
|
||||||
local dir = vim.fn.fnamemodify(path, ':h')
|
local dir = vim.fn.fnamemodify(path, ':h')
|
||||||
if vim.fn.isdirectory(dir) == 0 then
|
if vim.fn.isdirectory(dir) == 0 then
|
||||||
|
|
@ -54,31 +73,43 @@ local function save_json_file(path, data)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return pending.GcalCredentials?
|
||||||
local function load_credentials()
|
local function load_credentials()
|
||||||
local creds = load_json_file(credentials_path())
|
local creds = load_json_file(credentials_path())
|
||||||
if not creds then
|
if not creds then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
if creds.installed then
|
if creds.installed then
|
||||||
return creds.installed
|
return creds.installed --[[@as pending.GcalCredentials]]
|
||||||
end
|
end
|
||||||
return creds
|
return creds --[[@as pending.GcalCredentials]]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return pending.GcalTokens?
|
||||||
local function load_tokens()
|
local function load_tokens()
|
||||||
return load_json_file(token_path())
|
return load_json_file(token_path()) --[[@as pending.GcalTokens?]]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param tokens pending.GcalTokens
|
||||||
|
---@return boolean
|
||||||
local function save_tokens(tokens)
|
local function save_tokens(tokens)
|
||||||
return save_json_file(token_path(), tokens)
|
return save_json_file(token_path(), tokens)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param str string
|
||||||
|
---@return string
|
||||||
local function url_encode(str)
|
local function url_encode(str)
|
||||||
return str:gsub('([^%w%-%.%_%~])', function(c)
|
return str:gsub('([^%w%-%.%_%~])', function(c)
|
||||||
return string.format('%%%02X', string.byte(c))
|
return string.format('%%%02X', string.byte(c))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param method string
|
||||||
|
---@param url string
|
||||||
|
---@param headers? string[]
|
||||||
|
---@param body? string
|
||||||
|
---@return table? result
|
||||||
|
---@return string? err
|
||||||
local function curl_request(method, url, headers, body)
|
local function curl_request(method, url, headers, body)
|
||||||
local args = { 'curl', '-s', '-X', method }
|
local args = { 'curl', '-s', '-X', method }
|
||||||
for _, h in ipairs(headers or {}) do
|
for _, h in ipairs(headers or {}) do
|
||||||
|
|
@ -107,6 +138,8 @@ local function curl_request(method, url, headers, body)
|
||||||
return decoded, nil
|
return decoded, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param access_token string
|
||||||
|
---@return string[]
|
||||||
local function auth_headers(access_token)
|
local function auth_headers(access_token)
|
||||||
return {
|
return {
|
||||||
'Authorization: Bearer ' .. access_token,
|
'Authorization: Bearer ' .. access_token,
|
||||||
|
|
@ -114,6 +147,9 @@ local function auth_headers(access_token)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param creds pending.GcalCredentials
|
||||||
|
---@param tokens pending.GcalTokens
|
||||||
|
---@return pending.GcalTokens?
|
||||||
local function refresh_access_token(creds, tokens)
|
local function refresh_access_token(creds, tokens)
|
||||||
local body = 'client_id='
|
local body = 'client_id='
|
||||||
.. url_encode(creds.client_id)
|
.. url_encode(creds.client_id)
|
||||||
|
|
@ -142,13 +178,14 @@ local function refresh_access_token(creds, tokens)
|
||||||
if not ok or not decoded.access_token then
|
if not ok or not decoded.access_token then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
tokens.access_token = decoded.access_token
|
tokens.access_token = decoded.access_token --[[@as string]]
|
||||||
tokens.expires_in = decoded.expires_in
|
tokens.expires_in = decoded.expires_in --[[@as integer?]]
|
||||||
tokens.obtained_at = os.time()
|
tokens.obtained_at = os.time()
|
||||||
save_tokens(tokens)
|
save_tokens(tokens)
|
||||||
return tokens
|
return tokens
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return string?
|
||||||
local function get_access_token()
|
local function get_access_token()
|
||||||
local creds = load_credentials()
|
local creds = load_credentials()
|
||||||
if not creds then
|
if not creds then
|
||||||
|
|
@ -260,6 +297,10 @@ function M.authorize()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param creds pending.GcalCredentials
|
||||||
|
---@param code string
|
||||||
|
---@param code_verifier string
|
||||||
|
---@param port integer
|
||||||
function M._exchange_code(creds, code, code_verifier, port)
|
function M._exchange_code(creds, code, code_verifier, port)
|
||||||
local body = 'client_id='
|
local body = 'client_id='
|
||||||
.. url_encode(creds.client_id)
|
.. url_encode(creds.client_id)
|
||||||
|
|
@ -303,6 +344,9 @@ function M._exchange_code(creds, code, code_verifier, port)
|
||||||
vim.notify('pending.nvim: Google Calendar authorized successfully.')
|
vim.notify('pending.nvim: Google Calendar authorized successfully.')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param access_token string
|
||||||
|
---@return string? calendar_id
|
||||||
|
---@return string? err
|
||||||
local function find_or_create_calendar(access_token)
|
local function find_or_create_calendar(access_token)
|
||||||
local gc = gcal_config()
|
local gc = gcal_config()
|
||||||
local cal_name = gc.calendar or 'Pendings'
|
local cal_name = gc.calendar or 'Pendings'
|
||||||
|
|
@ -329,18 +373,25 @@ local function find_or_create_calendar(access_token)
|
||||||
return created and created.id, nil
|
return created and created.id, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param date_str 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)
|
return os.date('%Y-%m-%d', t) --[[@as string]]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param access_token string
|
||||||
|
---@param calendar_id string
|
||||||
|
---@param task pending.Task
|
||||||
|
---@return string? event_id
|
||||||
|
---@return string? err
|
||||||
local function create_event(access_token, calendar_id, task)
|
local function create_event(access_token, calendar_id, task)
|
||||||
local event = {
|
local event = {
|
||||||
summary = task.description,
|
summary = task.description,
|
||||||
start = { date = task.due },
|
start = { date = task.due },
|
||||||
['end'] = { date = next_day(task.due) },
|
['end'] = { date = next_day(task.due or '') },
|
||||||
transparency = 'transparent',
|
transparency = 'transparent',
|
||||||
extendedProperties = {
|
extendedProperties = {
|
||||||
private = { taskId = tostring(task.id) },
|
private = { taskId = tostring(task.id) },
|
||||||
|
|
@ -358,11 +409,16 @@ local function create_event(access_token, calendar_id, task)
|
||||||
return data and data.id, nil
|
return data and data.id, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param access_token string
|
||||||
|
---@param calendar_id string
|
||||||
|
---@param event_id string
|
||||||
|
---@param task pending.Task
|
||||||
|
---@return string? err
|
||||||
local function update_event(access_token, calendar_id, event_id, task)
|
local function update_event(access_token, calendar_id, event_id, task)
|
||||||
local event = {
|
local event = {
|
||||||
summary = task.description,
|
summary = task.description,
|
||||||
start = { date = task.due },
|
start = { date = task.due },
|
||||||
['end'] = { date = next_day(task.due) },
|
['end'] = { date = next_day(task.due or '') },
|
||||||
}
|
}
|
||||||
local _, err = curl_request(
|
local _, err = curl_request(
|
||||||
'PATCH',
|
'PATCH',
|
||||||
|
|
@ -373,6 +429,10 @@ local function update_event(access_token, calendar_id, event_id, task)
|
||||||
return err
|
return err
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param access_token string
|
||||||
|
---@param calendar_id string
|
||||||
|
---@param event_id string
|
||||||
|
---@return string? err
|
||||||
local function delete_event(access_token, calendar_id, event_id)
|
local function delete_event(access_token, calendar_id, event_id)
|
||||||
local _, err = curl_request(
|
local _, err = curl_request(
|
||||||
'DELETE',
|
'DELETE',
|
||||||
|
|
@ -399,25 +459,25 @@ function M.sync()
|
||||||
|
|
||||||
for _, task in ipairs(tasks) do
|
for _, task in ipairs(tasks) do
|
||||||
local extra = task._extra or {}
|
local extra = task._extra or {}
|
||||||
local event_id = extra._gcal_event_id
|
local event_id = extra['_gcal_event_id'] --[[@as string?]]
|
||||||
|
|
||||||
local should_delete = event_id
|
local should_delete = event_id ~= nil
|
||||||
and (
|
and (
|
||||||
task.status == 'done'
|
task.status == 'done'
|
||||||
or task.status == 'deleted'
|
or task.status == 'deleted'
|
||||||
or (task.status == 'pending' and not task.due)
|
or (task.status == 'pending' and not task.due)
|
||||||
)
|
)
|
||||||
|
|
||||||
if should_delete then
|
if should_delete and event_id then
|
||||||
local del_err = delete_event(access_token, calendar_id, event_id)
|
local del_err = delete_event(access_token, calendar_id, event_id) --[[@as string]]
|
||||||
if not del_err then
|
if not del_err then
|
||||||
extra._gcal_event_id = nil
|
extra['_gcal_event_id'] = nil
|
||||||
if next(extra) == nil then
|
if next(extra) == nil then
|
||||||
task._extra = nil
|
task._extra = nil
|
||||||
else
|
else
|
||||||
task._extra = extra
|
task._extra = extra
|
||||||
end
|
end
|
||||||
task.modified = tostring(os.date('!%Y-%m-%dT%H:%M:%SZ'))
|
task.modified = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]]
|
||||||
deleted = deleted + 1
|
deleted = deleted + 1
|
||||||
end
|
end
|
||||||
elseif task.status == 'pending' and task.due then
|
elseif task.status == 'pending' and task.due then
|
||||||
|
|
@ -432,8 +492,8 @@ function M.sync()
|
||||||
if not task._extra then
|
if not task._extra then
|
||||||
task._extra = {}
|
task._extra = {}
|
||||||
end
|
end
|
||||||
task._extra._gcal_event_id = new_id
|
task._extra['_gcal_event_id'] = new_id
|
||||||
task.modified = tostring(os.date('!%Y-%m-%dT%H:%M:%SZ'))
|
task.modified = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]]
|
||||||
created = created + 1
|
created = created + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue