- Extract JASPER_URL into a shared constants module - Pass login credentials via a temp curl config file to avoid exposure in the process argument list (ps/proc) - Replace vim.ui.input secret prompt with vim.fn.inputsecret() - Add -s (silent) flag to all curl calls to suppress progress output - Guard curl output parser against missing newline in stdout - Track per-activity shared timestamp file (/tmp/jasper_<id>.last_activity) so the inactivity watchdog skips auto-pause when another Neovim instance is still active on the same task - Clean up leftover uv_timer on repeated begin_tracking calls - Remove shared activity file on teardown only when this instance wrote it
176 lines
4.6 KiB
Lua
176 lines
4.6 KiB
Lua
--- jasper/api.lua
|
|
--- Low-level HTTP calls to the Jasper API, executed via curl.
|
|
|
|
local M = {}
|
|
|
|
local constants = require("jasper.constants")
|
|
local JASPER_URL = constants.JASPER_URL
|
|
|
|
--- Run a curl command and return the parsed JSON body + HTTP status code.
|
|
--- @param args string[] full curl argument list (without the URL, which is last)
|
|
--- @return table|nil decoded JSON body
|
|
--- @return number HTTP status code (0 on curl error)
|
|
--- @return string|nil error message
|
|
local function curl(args)
|
|
-- Append -w to get the HTTP status on a separate last line
|
|
table.insert(args, "-w")
|
|
table.insert(args, "\n%{http_code}")
|
|
|
|
local result = vim.system(args, { text = true }):wait()
|
|
if result.code ~= 0 then
|
|
return nil, 0, "curl error (exit " .. result.code .. ")"
|
|
end
|
|
|
|
-- Split body and status code
|
|
local last_newline = result.stdout:match(".*\n()")
|
|
if not last_newline then
|
|
return nil, 0, "curl: unexpected output format (no newline)"
|
|
end
|
|
local status_code = tonumber(result.stdout:sub(last_newline)) or 0
|
|
local body = result.stdout:sub(1, last_newline - 2) -- strip trailing \n + status line
|
|
|
|
local ok, data = pcall(vim.fn.json_decode, body)
|
|
if not ok then
|
|
return nil, status_code, "JSON decode error"
|
|
end
|
|
return data, status_code, nil
|
|
end
|
|
|
|
--- @param path string API path, e.g. "/api/v1/timer/"
|
|
--- @param token string
|
|
--- @return table|nil, number, string|nil
|
|
local function get(path, token)
|
|
return curl({
|
|
"curl",
|
|
"-s",
|
|
"-H",
|
|
"Authorization: Token " .. token,
|
|
JASPER_URL .. path,
|
|
})
|
|
end
|
|
|
|
--- @param path string
|
|
--- @param token string
|
|
--- @param form table<string,any>|nil key/value pairs sent as form data
|
|
--- @return table|nil, number, string|nil
|
|
local function post(path, token, form)
|
|
local args = {
|
|
"curl",
|
|
"-s",
|
|
"-X",
|
|
"POST",
|
|
"-H",
|
|
"Authorization: Token " .. token,
|
|
}
|
|
if form then
|
|
for k, v in pairs(form) do
|
|
table.insert(args, "--data-urlencode")
|
|
table.insert(args, k .. "=" .. tostring(v))
|
|
end
|
|
end
|
|
table.insert(args, JASPER_URL .. path)
|
|
return curl(args)
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Public API
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
--- Fetch all timers for the current user.
|
|
--- @param token string
|
|
--- @return table[]|nil list of timer objects
|
|
--- @return string|nil error message
|
|
function M.get_timers(token)
|
|
local data, status, err = get("/api/v1/timer/", token)
|
|
if err then
|
|
return nil, err
|
|
end
|
|
if status == 401 then
|
|
return nil, "unauthorized"
|
|
end
|
|
if status ~= 200 then
|
|
return nil, "unexpected HTTP status " .. status
|
|
end
|
|
if not data then
|
|
return nil, "empty response"
|
|
end
|
|
local timers_dict = data.data and data.data.timers
|
|
if type(timers_dict) ~= "table" then
|
|
return nil, "unexpected response shape"
|
|
end
|
|
local timers = {}
|
|
for _, v in pairs(timers_dict) do
|
|
table.insert(timers, v)
|
|
end
|
|
return timers, nil
|
|
end
|
|
|
|
--- Start (play) a timer.
|
|
--- @param token string
|
|
--- @param timer_id number
|
|
--- @return boolean ok
|
|
--- @return string|nil error
|
|
function M.start_timer(token, timer_id)
|
|
local data, status, err = post("/api/v1/timer/" .. timer_id .. "/play/", token)
|
|
if err then
|
|
return false, err
|
|
end
|
|
if status ~= 200 then
|
|
return false, "HTTP " .. status
|
|
end
|
|
if not data then
|
|
return true, nil
|
|
end
|
|
if data.error ~= nil and data.error ~= vim.NIL then
|
|
return false, tostring(data.error)
|
|
end
|
|
return true, nil
|
|
end
|
|
|
|
--- Pause a timer.
|
|
--- @param token string
|
|
--- @param timer_id number
|
|
--- @return boolean ok
|
|
--- @return string|nil error
|
|
function M.stop_timer(token, timer_id)
|
|
local data, status, err = post("/api/v1/timer/" .. timer_id .. "/pausa/", token)
|
|
if err then
|
|
return false, err
|
|
end
|
|
if status ~= 200 then
|
|
return false, "HTTP " .. status
|
|
end
|
|
if not data then
|
|
return true, nil
|
|
end
|
|
if data.error ~= nil and data.error ~= vim.NIL then
|
|
return false, tostring(data.error)
|
|
end
|
|
return true, nil
|
|
end
|
|
|
|
--- Associate an attivita with a (new) timer slot.
|
|
--- @param token string
|
|
--- @param timer_id number desired slot id (smallest free integer)
|
|
--- @param attivita_id number
|
|
--- @return boolean ok
|
|
--- @return string|nil error
|
|
function M.create_timer(token, timer_id, attivita_id)
|
|
local data, status, err = post("/api/v1/timer/" .. timer_id .. "/attivita/", token, { attivita_id = attivita_id })
|
|
if err then
|
|
return false, err
|
|
end
|
|
if status ~= 200 then
|
|
return false, "HTTP " .. status
|
|
end
|
|
if not data then
|
|
return true, nil
|
|
end
|
|
if data.error ~= nil and data.error ~= vim.NIL then
|
|
return false, tostring(data.error)
|
|
end
|
|
return true, nil
|
|
end
|
|
|
|
return M
|