|
|
@@ -359,6 +359,117 @@ namespace ClankerSaddle.Api {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * `GET /api/saddle/tasks?project_id=N&q=free-text` — the task-reading
|
|
|
+ * API behind the daemon's `saddle tasks` CLI (wire contract):
|
|
|
+ * authenticated by the dedicated callback token (like
|
|
|
+ * `/api/saddle/note`). With `project_id` only that project's tasks;
|
|
|
+ * `q` matches title, description or plan (case-insensitive
|
|
|
+ * substring). Compact rows — id, title, status, parent_task_id.
|
|
|
+ */
|
|
|
+ public class SaddleTaskListEndpoint : Object, Endpoint {
|
|
|
+
|
|
|
+ private SaddleService saddles = inject<SaddleService>();
|
|
|
+ private TaskService tasks = inject<TaskService>();
|
|
|
+
|
|
|
+ public virtual async HttpResult handle_request(HttpContext http_context,
|
|
|
+ RouteContext route_context) throws Error {
|
|
|
+ if (!(yield saddles.validate_callback_token(bearer_token(http_context)))) {
|
|
|
+ return SaddleUpdateEndpoint.json_error(401, "Missing or invalid callback token");
|
|
|
+ }
|
|
|
+
|
|
|
+ string project_text = "";
|
|
|
+ int64? project_id = null;
|
|
|
+ if (http_context.request.query_params.try_get_any("project_id", out project_text)
|
|
|
+ && project_text != null && project_text.length > 0) {
|
|
|
+ project_id = int64.parse(project_text);
|
|
|
+ }
|
|
|
+ string q = "";
|
|
|
+ if (!http_context.request.query_params.try_get_any("q", out q) || q == null) {
|
|
|
+ // try_get_any leaves the out parameter null when absent.
|
|
|
+ q = "";
|
|
|
+ }
|
|
|
+ q = q.strip().down();
|
|
|
+
|
|
|
+ try {
|
|
|
+ var rows = yield tasks.list_tasks(null, project_id);
|
|
|
+ var array = new Json.Array();
|
|
|
+ foreach (var task in rows) {
|
|
|
+ if (q.length > 0) {
|
|
|
+ string haystack = (task.title + "\n"
|
|
|
+ + (task.description ?? "") + "\n"
|
|
|
+ + (task.plan ?? "")).down();
|
|
|
+ if (!(q in haystack)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var element = new Json.Object();
|
|
|
+ element.set_int_member("id", task.id);
|
|
|
+ element.set_string_member("title", task.title);
|
|
|
+ element.set_string_member("status", task.status);
|
|
|
+ element.set_int_member("parent_task_id", task.parent_task_id);
|
|
|
+ array.add_object_element(element);
|
|
|
+ }
|
|
|
+ var body = new Json.Object();
|
|
|
+ body.set_array_member("tasks", array);
|
|
|
+ return SaddleUpdateEndpoint.json_ok(body);
|
|
|
+ } catch (Error e) {
|
|
|
+ return SaddleUpdateEndpoint.json_error(400, e.message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * `GET /api/saddle/task/{id}` — the single-task view behind the
|
|
|
+ * daemon's `saddle task` CLI (wire contract): callback-token
|
|
|
+ * authenticated, joining the parent's title for lineage context.
|
|
|
+ * 404 when the task does not exist.
|
|
|
+ */
|
|
|
+ public class SaddleTaskDetailEndpoint : Object, Endpoint {
|
|
|
+
|
|
|
+ private SaddleService saddles = inject<SaddleService>();
|
|
|
+ private TaskService tasks = inject<TaskService>();
|
|
|
+
|
|
|
+ public virtual async HttpResult handle_request(HttpContext http_context,
|
|
|
+ RouteContext route_context) throws Error {
|
|
|
+ if (!(yield saddles.validate_callback_token(bearer_token(http_context)))) {
|
|
|
+ return SaddleUpdateEndpoint.json_error(401, "Missing or invalid callback token");
|
|
|
+ }
|
|
|
+
|
|
|
+ string id_text = "";
|
|
|
+ route_context.mapped_parameters.try_get("id", out id_text);
|
|
|
+ int64 task_id = int64.parse(id_text);
|
|
|
+
|
|
|
+ try {
|
|
|
+ var detail = yield tasks.get_task(task_id);
|
|
|
+ if (detail == null) {
|
|
|
+ return SaddleUpdateEndpoint.json_error(404,
|
|
|
+ @"No task exists with id $(task_id)");
|
|
|
+ }
|
|
|
+ var task = ((!)detail).task;
|
|
|
+ string parent_title = "";
|
|
|
+ if (task.parent_task_id > 0) {
|
|
|
+ var parent = yield tasks.get_task(task.parent_task_id);
|
|
|
+ parent_title = parent != null ? ((!)parent).task.title : "";
|
|
|
+ }
|
|
|
+ var element = new Json.Object();
|
|
|
+ element.set_int_member("id", task.id);
|
|
|
+ element.set_string_member("title", task.title);
|
|
|
+ element.set_string_member("description", task.description ?? "");
|
|
|
+ element.set_string_member("status", task.status);
|
|
|
+ element.set_string_member("plan", task.plan ?? "");
|
|
|
+ element.set_string_member("summary", task.summary ?? "");
|
|
|
+ element.set_int_member("parent_task_id", task.parent_task_id);
|
|
|
+ element.set_string_member("parent_title", parent_title);
|
|
|
+ var body = new Json.Object();
|
|
|
+ body.set_object_member("task", element);
|
|
|
+ return SaddleUpdateEndpoint.json_ok(body);
|
|
|
+ } catch (Error e) {
|
|
|
+ return SaddleUpdateEndpoint.json_error(400, e.message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* `POST /api/saddle/subtask` — the synchronous subtask-creation
|
|
|
* callback a daemon's `saddle subtask` CLI posts to (wire contract):
|