Vikunja
Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.
Crate: adapter-vikunja · Capabilities: tasks
Vikunja is an open-source, self-hosted task manager. The adapter is tasks-only and is one of the two “collaborative tasks” backends (assignment + membership), alongside Todoist.
Authentication
Section titled “Authentication”A personal API token the user mints in Vikunja, sent as a Bearer
token. The server base URL is user-supplied (self-hosted or hosted
instance).
Token permissions are per route and fixed when the token is created. Routes
that exist only in v2 carry permissions of their own, and a token made
before the server ran 2.4 never has them: the assignee pool
(GET /api/v2/projects/{id}/users/search) needs users search under
Projects. Vikunja answers a missing permission and an expired token with
the same 401 (code 11), so the adapter reports that read as
cal_core::ReadRefusal::TokenRefused with the permission as the detail, and
the editors name it instead of hiding the field (decision 130).
API versions
Section titled “API versions”Vikunja 2.4.0 introduced API v2 and froze v1 (deprecated in 3.0,
removed in 4.0). The adapter speaks both: it probes
GET /api/v2/projects once per client (concurrent first calls share
one probe) and pins v2 only on positive evidence that Vikunja’s v2
router answered — a JSON body (the list envelope, or a problem+json
auth error), which the things standing in front of a server (SPA
fallbacks, auth gates, WAFs) never produce. 404/405 means a pre-2.4
server and pins v1; other non-JSON answers conservatively pin v1;
transient answers (5xx, 408/429) and transport errors propagate
without pinning, so the next call re-probes. Version-dependent
behaviour funnels through semantic client helpers (create_json,
update_json, get_page), so call sites stay verb-agnostic. The
differences the adapter bridges:
- Verbs: v1’s unusual
PUT= create /POST= update becomes conventionalPOST= create /PATCH= update (JSON Merge Patch, sent asContent-Type: application/merge-patch+json— v2 dispatches its patch dialects by media type). PATCH is chosen over v2’s full-replacePUTso fields Aperio doesn’t model (reminders, favourites) survive an update. - Clear semantics: v1 cleared omitted fields (fresh-struct decode);
merge patch keeps them. Fields our body omits with clear intent —
the three dates and an emptied description — are sent as explicit
nullon v2. - Lists become pagination envelopes (
{ items, total_pages, … }) instead of bare arrays with header-based paging; walkers trusttotal_pageson v2 (the short-page heuristic only decides when no count was given),items: null(Go’s nil slice) reads as empty, and endpoints that were unpaginated on v1 (views, buckets, members, shares) are walked page by page on v2. - Strict schemas: v2 request bodies are validated with
additionalProperties: false. The share bodies drop the legacyuser_id/rightcompatibility keys on v2 (v1 keeps sending old + new names so legacy servers bind them). - Renamed/moved endpoints:
/projects/{id}/projectusers→/projects/{id}/users/search; user search?s=→?q=; bucket move and assignee-bulk go fromPOSTtoPUT(same path/body); share right-change goes fromPOSTtoPUT; bucket rename only has a full-replacePUTon v2, so the current bucket is read first and itspositionand WIPlimitsent along (a bucket the read-back can’t find is an error — replacing blind would zero both). - Errors are RFC 9457 problem+json; validation failures report 422
where v1 used 412 (both map to
Conflict).
Data model mapping
Section titled “Data model mapping”- Projects → task lists. Projects nest via
parent_project_id(surfaced asTaskList.parent_id), so the sidebar renders the tree. - Buckets → sections (per-project kanban view); degrades to “no sections” on servers that don’t expose the view/bucket endpoints.
- Tasks:
start_date/due_datemap onto Aperio’sscheduled_*/deadline_*. Vikunja uses an RFC-3339 sentinel (0001-01-01T…) for “no date” — treated as unset on read, emitted on write when a slot is empty. - Priority maps by label: Vikunja 1/2/3 (Low/Medium/High) ↔ Aperio Low/Medium/High. Unset (0) reads as Low; Urgent (4) and DO NOW (5) collapse to High and write back as High (3) — Aperio has only three levels.
- Status / in-progress. Vikunja has no boolean “in progress”, only
done+percent_done. Aperio ridespercent_done: Completed →done = true; InProgress →done = false,percent_done = 0.5; Open →done = false,percent_done = 0. On read a not-done task with any progress is InProgress, so a task nudged to e.g. 50% in Vikunja’s own UI round-trips too, and the three-step check-off works (supports_in_progress: true). Cancelled has no Vikunja equivalent (done = false,percent_done = 0) — the marker stays local. - Subtasks are task relations, not a task field: the child’s
parenttaskrelation (created on/tasks/{child}/relations, removed viaDELETE /tasks/{child}/relations/parenttask/{parent}) maps ontoTask.parent_id; reads take the first non-zerorelated_tasks.parenttaskentry. Updates reconcile against an authoritativeGET /tasks/{id}— Vikunja populatesrelated_tasksonly on reads, never on the update echo — unlink every stale parent (Vikunja allows several), and treat create→409 (already linked) / DELETE→404 (already gone) as success.
Collaboration (assignment + membership)
Section titled “Collaboration (assignment + membership)”- Assignees (multi): read inline on the task; written via the bulk
endpoint
…/tasks/{id}/assignees/bulk(v1POST, v2PUT; replace-semantics — the full desired set). The assigneeTaskUser.idis the numeric user id. - Member pool for the picker:
GET /projects/{id}/projectuserson v1,GET /projects/{id}/users/searchon v2. - Own identity (“me”):
GET /user. - Membership/sharing:
GET /projects/{id}/userslists direct shares with rights (read/write/admin → 0/1/2); add/remove/change-right on/projects/{id}/users(create and right-change follow the version’s verb scheme — see “API versions”). Sharing is immediate to existing users — no invitation flow. Membership keys on the username, distinct from the numeric-id assignee key. - User search:
GET /users?s=(v1) /GET /users?q=(v2) for the add-member dialog.
Testing
Section titled “Testing”mockito with canned Vikunja JSON. The v1 suite pins the client to v1
(its mocks use /api/v1/... paths verbatim); a v2 suite covers the
envelope pagination (both the stop and the continue direction, plus
items: null), the verb swaps (create, update, bucket move, assignee
bulk, shares), the explicit-null merge-patch body and its
application/merge-patch+json content type, the strict share bodies
(exact-matched), and the position+limit-preserving bucket rename; the
detection probe has its own tests (JSON vs HTML answers, 404/405,
5xx-no-pin-then-retry). Tests cover list/project mapping, the date
sentinel, the bucket→section fallback, and the assignee/membership
endpoints. Live testing: any Vikunja instance with an API token.