# NisTaskRest REST API data bridge for NIS tasks — converts JSON string-keyed params to NisTask calls, adds convenience features like natural language capture, daily planning, and snooze. --- ## Table of Contents 1. [Overview](#overview) 2. [Features](#features) 3. [Usage](#usage) 4. [API Reference](#api-reference) 5. [Param Parsing](#param-parsing) 6. [Related Documentation](#related-documentation) --- ## Overview `NisTaskRest` is a pure functional module (no GenServer, no state) that bridges JSON-based REST/MCP inputs to `NisTask`. Beyond simple type coercion, it adds higher-level features that combine multiple NisTask calls — daily planning, natural language capture via `TaskCapture`, smart completion (auto-detects recurring tasks), and snooze with natural language time expressions. The module follows the same pattern as `NisRest`: the router handles HTTP, `NisTaskRest` handles data transformation and orchestration, and `NisTask` handles raw database operations. ## Features | Feature | Description | |---------|-------------| | CRUD | List, get, create, update, delete tasks | | Smart complete | Auto-detects recurring tasks and creates next instance | | Pushover notify | Alarm management on update/complete/delete; auto-alarm on capture when `due_at` is set | | Capture | Natural language task creation via TaskCapture | | Daily plan | Aggregated view: overdue, due today, recurring, becoming active, focus task | | Snooze | Natural language snooze (`"tomorrow"`, `"in 2 hours"`, `"next monday"`) | | Dashboard | Stats + due today + overdue + blocked + completed today | | Entity linking | Link/unlink tasks to contacts, companies, etc. | | Query proxy | Execute named NisTaskWeb queries with params | ## Usage ### Via MCP Tools ```elixir NisTaskRest.create("james", %{"title" => "Review PR", "priority" => "7", "due_at" => "1709164800"}) NisTaskRest.capture("james", "buy groceries tomorrow #home !!") NisTaskRest.snooze("james", task_id, "next monday", "Asia/Ho_Chi_Minh") ``` ### Via REST Router ```elixir NisTaskRest.daily_plan("james", "Asia/Ho_Chi_Minh") NisTaskRest.dashboard("james", "Asia/Ho_Chi_Minh") NisTaskRest.query("james", "overdue", %{}, "Asia/Ho_Chi_Minh") ``` ## API Reference ### `list/2` List tasks with optional status and tag filters. **Parameters:** - `username` (string) - `params` (map, optional) — `"status"` (filters by status), `"tag"` (filters by tag) **Returns:** list of task maps Filter logic: - `tag` + `status` → open tasks with tag, filtered by status - `tag` only → open tasks with tag - `status: "completed"` → completed tasks - `status: "blocked"` → blocked tasks - other `status` → open tasks filtered to that status - no filters → all open tasks ```elixir tasks = NisTaskRest.list("james", %{"status" => "todo", "tag" => "work"}) ``` ### `get/2` Fetch a task by ID, including subtasks. **Parameters:** - `username` (string) - `id` (integer) **Returns:** `{:ok, map}` (with `"subtasks"` key) or `{:error, reason}` ```elixir {:ok, task} = NisTaskRest.get("james", 42) # task["subtasks"] => [%{"title" => "Subtask 1", ...}] ``` ### `create/2` Create a task from JSON params. **Parameters:** - `username` (string) - `params` (map) — `"title"`, `"note"`, `"waiting_for"`, `"status"`, `"priority"`, `"parent_id"`, `"due_at"`, `"after_at"`, `"expires_at"`, `"completed_at"`, `"tags"`, `"recur"` **Returns:** `{:ok, task_map}` (the created task) or `{:error, reason}` ```elixir {:ok, task} = NisTaskRest.create("james", %{ "title" => "Ship v2.0", "priority" => "9", "due_at" => "1709164800", "tags" => "deploy,urgent" }) ``` ### `update/3` Update a task from JSON params. If `notify` is included, manages the Pushover alarm: - `notify=1` + `due_at` → cancels old alarm (if any), creates new Alarm → Pushover at due time, stores `alarm_id` - `notify=0` + existing alarm → cancels alarm, clears `alarm_id` - No `notify` param → leaves alarm unchanged **Parameters:** - `username` (string) - `id` (integer) - `params` (map) — any task field plus optional `"notify"` (0 or 1) **Returns:** `{:ok, task_map}` (the updated task) or `{:error, reason}` ```elixir # Update with Pushover notification at due time {:ok, task} = NisTaskRest.update("james", 42, %{"priority" => "10", "notify" => 1}) # Disable notification {:ok, task} = NisTaskRest.update("james", 42, %{"notify" => 0}) ``` ### `complete/2` Smart completion — detects recurring tasks and auto-creates the next instance. Cancels any active Pushover alarm. For recurring tasks with an alarm, the alarm is propagated to the next instance. **Parameters:** - `username` (string) - `id` (integer) **Returns:** `{:ok, task_map}` (for non-recurring) or `{:ok, %{completed: map, next: map}}` (for recurring) ```elixir # Non-recurring {:ok, completed_task} = NisTaskRest.complete("james", 42) # Recurring (auto-detected) {:ok, %{completed: done, next: new_task}} = NisTaskRest.complete("james", 99) ``` ### `delete/2` Delete a task. Cancels any active Pushover alarm. **Returns:** `{:ok, %{deleted: count}}` or `{:error, reason}` ```elixir {:ok, %{deleted: 1}} = NisTaskRest.delete("james", 42) ``` ### `capture/3` Create a task from natural language using `TaskCapture.parse/2`. When the parsed result has a `due_at`, a Pushover alarm is automatically created at that time. **Parameters:** - `username` (string) - `text` (string) — natural language task description - `tz` (string, optional) — timezone, defaults to `"Etc/UTC"` **Returns:** `{:ok, task_map}` or `{:error, reason}` ```elixir {:ok, task} = NisTaskRest.capture("james", "buy groceries tomorrow #home !!", "Asia/Ho_Chi_Minh") # Parses to: %{title: "buy groceries", due_at: ..., tags: ["home"], priority: 5} # Auto-creates Pushover alarm at due time ``` ### `daily_plan/2` Generate a comprehensive daily plan. **Parameters:** - `username` (string) - `tz` (string, optional) — timezone, defaults to `"Etc/UTC"` **Returns:** map with keys: | Key | Description | |-----|-------------| | `:overdue` | Tasks past their due date | | `:due_today` | Tasks due today | | `:recurring_today` | Recurring tasks due today | | `:becoming_active` | Tasks whose `after_at` falls today | | `:focus` | Single highest-priority actionable task | | `:upcoming` | Tasks due in next 2-3 days (excludes today) | | `:stats` | `%{completed_yesterday, completed_this_week, overdue, open}` | ```elixir plan = NisTaskRest.daily_plan("james", "Asia/Ho_Chi_Minh") ``` ### `snooze/4` Snooze a task using natural language time. **Parameters:** - `username` (string) - `id` (integer) - `until` (string) — natural language time (`"tomorrow"`, `"in 2 hours"`, `"next monday"`) - `tz` (string, optional) — timezone, defaults to `"Etc/UTC"` **Returns:** `{:ok, task_map}` or `{:error, reason}` ```elixir {:ok, task} = NisTaskRest.snooze("james", 42, "next monday", "Asia/Ho_Chi_Minh") ``` ### `complete_recurring/4` Complete a recurring task with optional note, creating the next instance. If the completed task had a Pushover alarm, the alarm is propagated to the next instance. **Parameters:** - `username` (string) - `id` (integer) - `tz` (string, optional) — defaults to `"Etc/UTC"` - `note` (string, optional) — completion note **Returns:** `{:ok, %{completed: map, next: map | nil}}` ```elixir {:ok, result} = NisTaskRest.complete_recurring("james", 42, "Asia/Ho_Chi_Minh", "Done early") ``` ### `query/4` Execute a named query from `NisTaskWeb`. **Parameters:** - `username` (string) - `name` (string) — query name (e.g. `"overdue"`, `"due_today"`, `"blocked"`) - `params` (map, optional) - `tz` (string, optional) **Returns:** query results ```elixir results = NisTaskRest.query("james", "overdue") results = NisTaskRest.query("james", "open_tagged", %{"tag" => "work"}) ``` ### `query_index/0` Get available query names and their parameter requirements. **Returns:** `%{categories: %{...}, param_queries: %{...}}` ```elixir %{categories: cats, param_queries: pqs} = NisTaskRest.query_index() ``` ### `dashboard/2` Get a task dashboard summary. **Parameters:** - `username` (string) - `tz` (string, optional) **Returns:** map with keys: | Key | Description | |-----|-------------| | `:stats` | `%{open, overdue, due_today, blocked, completed_today}` | | `:due_today` | Tasks due today | | `:overdue` | Overdue tasks | | `:blocked` | Blocked tasks | | `:completed_today` | Tasks completed today | ```elixir dash = NisTaskRest.dashboard("james", "Asia/Ho_Chi_Minh") ``` ### Entity Linking #### `link/3` Link a task to an entity. **Parameters:** - `username` (string) - `task_id` (integer) - `params` (map) — `"entity_type"` and `"entity_id"` required **Returns:** `{:ok, :linked}` or `{:error, reason}` ```elixir {:ok, :linked} = NisTaskRest.link("james", 42, %{"entity_type" => "contact", "entity_id" => "99"}) ``` #### `unlink/3` Remove a task-entity link. **Returns:** `:ok` or `{:error, reason}` ```elixir :ok = NisTaskRest.unlink("james", 42, %{"entity_type" => "contact", "entity_id" => "99"}) ``` #### `links_for/2` Get all entity links for a task. **Returns:** `{:ok, list}` ```elixir {:ok, links} = NisTaskRest.links_for("james", 42) ``` #### `tasks_for_entity/2` Get all tasks linked to an entity. **Params:** `"entity_type"` and `"entity_id"` required **Returns:** `{:ok, list}` or `{:error, reason}` ```elixir {:ok, tasks} = NisTaskRest.tasks_for_entity("james", %{"entity_type" => "contact", "entity_id" => "99"}) ``` ## Param Parsing Same type coercion pattern as `NisRest`: | Helper | Behavior | |--------|----------| | `put_string` | Trims whitespace, drops nil/empty | | `put_integer` | Parses string integers, truncates floats | | `put_time` | Accepts Unix integers, ISO 8601 strings | | `put_json_tags` | Splits comma-separated strings into list | | `put_integer(:notify)` | Parses `0`/`1` for Pushover alarm toggle | Time fields (`due_at`, `after_at`, `expires_at`, `completed_at`) accept both Unix timestamp strings (`"1709164800"`) and ISO 8601 (`"2024-02-28T14:00"`). ## Related Documentation - [NisTask](nis_task.md) — core task management module - [NisRest](nis_rest.md) — REST bridge for NIS entities - [TaskCapture](task_capture.md) — natural language parser used by `capture/3` - [TaskRecurrence](task_recurrence.md) — recurrence engine --- *Source: `lib/nis_task_rest.ex` — Last updated: 2026-03-03*