87 lines
3.6 KiB
TypeScript
87 lines
3.6 KiB
TypeScript
|
|
// *************************************************************************
|
||
|
|
// * (C)opyright 2026 by Ruben Carlo Benante *
|
||
|
|
// * *
|
||
|
|
// * This program is free software; you can redistribute it and/or modify *
|
||
|
|
// * it under the terms of the GNU General Public License as published by *
|
||
|
|
// * the Free Software Foundation, either version 3 of the License, or *
|
||
|
|
// * (at your option) any later version. *
|
||
|
|
// * *
|
||
|
|
// * This program is distributed in the hope that it will be useful, *
|
||
|
|
// * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||
|
|
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||
|
|
// * GNU General Public License for more details. *
|
||
|
|
// * *
|
||
|
|
// * You should have received a copy of the GNU General Public License *
|
||
|
|
// * along with this program. If not, see http://www.gnu.org/licenses/. *
|
||
|
|
// * *
|
||
|
|
// * Contact author at: *
|
||
|
|
// * Ruben Carlo Benante *
|
||
|
|
// * rcb@beco.cc *
|
||
|
|
// *************************************************************************
|
||
|
|
|
||
|
|
// The only module that touches the network. Uses Obsidian's requestUrl (not
|
||
|
|
// fetch) so the HTTP Basic auth header and cross-origin call work. The pure
|
||
|
|
// request/response helpers live in rpc.ts and are unit-tested there.
|
||
|
|
|
||
|
|
import { requestUrl } from 'obsidian';
|
||
|
|
import { buildJsonRpcRequest, parseRpcResult, type RpcAuth } from './rpc';
|
||
|
|
|
||
|
|
// Perform a JSON-RPC call and return its `result`, throwing a readable Error
|
||
|
|
// on transport failures or Kanboard-reported errors.
|
||
|
|
async function call(
|
||
|
|
auth: RpcAuth,
|
||
|
|
method: string,
|
||
|
|
params: Record<string, unknown>,
|
||
|
|
): Promise<unknown> {
|
||
|
|
const req = buildJsonRpcRequest(auth, method, params);
|
||
|
|
const resp = await requestUrl({
|
||
|
|
url: req.url,
|
||
|
|
method: 'POST',
|
||
|
|
headers: req.headers,
|
||
|
|
body: req.body,
|
||
|
|
throw: false,
|
||
|
|
});
|
||
|
|
if (resp.status === 401) {
|
||
|
|
throw new Error('Authentication failed (check username and API token)');
|
||
|
|
}
|
||
|
|
let data: unknown;
|
||
|
|
try {
|
||
|
|
data = resp.json;
|
||
|
|
} catch {
|
||
|
|
throw new Error(`Unexpected response from Kanboard (HTTP ${resp.status})`);
|
||
|
|
}
|
||
|
|
return parseRpcResult(data);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Resolve a board (Kanboard project) display name to its numeric id, or null.
|
||
|
|
export async function getProjectIdByName(
|
||
|
|
auth: RpcAuth,
|
||
|
|
name: string,
|
||
|
|
): Promise<number | null> {
|
||
|
|
const result = await call(auth, 'getProjectByName', { name });
|
||
|
|
if (!result || typeof result !== 'object') return null;
|
||
|
|
const id = (result as { id?: unknown }).id;
|
||
|
|
return id ? Number(id) : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create a Kanboard task (which QualKard adopts as a new "new" card). Returns
|
||
|
|
// the new task id. project_id + title are the only required fields; QualKard's
|
||
|
|
// server-side hook fills in the spaced-repetition state on creation.
|
||
|
|
export async function createTask(
|
||
|
|
auth: RpcAuth,
|
||
|
|
projectId: number,
|
||
|
|
title: string,
|
||
|
|
description: string,
|
||
|
|
): Promise<number> {
|
||
|
|
const result = await call(auth, 'createTask', {
|
||
|
|
title,
|
||
|
|
project_id: projectId,
|
||
|
|
description,
|
||
|
|
});
|
||
|
|
// createTask returns the new task id, or false on a validation failure.
|
||
|
|
if (!result || typeof result === 'boolean') {
|
||
|
|
throw new Error('Kanboard rejected the task (no id returned)');
|
||
|
|
}
|
||
|
|
return Number(result);
|
||
|
|
}
|