0.0
The project is in a healthy, maintained state
An asynchronous Ruby client for the Vikunja API.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 0.20
~> 0.2
 Project Readme

async-vikunja

gem "async-vikunja"
require "async/vikunja"

Async::Vikunja::Client.open("https://vikunja.example.com/api/v2/") do |client|
  vikunja = client.authenticated(ENV.fetch("VIKUNJA_TOKEN"))

  vikunja.info.version
  vikunja.user.username
  vikunja.test_token.message
end
Async::Vikunja::Client.open do |client|
  vikunja = client.authenticated(ENV.fetch("VIKUNJA_TOKEN"))
end
jwt = Async::Vikunja::Client.open(url) do |client|
  client.login({username: "tester", password: "secret", long_token: true}).token
end
vikunja.projects.map(&:title)
vikunja.projects(q: "inbox").first.id
vikunja.projects(is_archived: true).count

vikunja.projects.each_page(per_page: 25) do |page|
  page.page
  page.total
  page.total_pages
  page.items.size
  page.metadata["content-type"]

  page.each{|project| project.title}
end

vikunja.projects.take(2).map(&:title)
project = vikunja.create_project({title: "Inbox", hex_color: "4287f5"})

project.id
project.identifier
project.owner.username
project.views.map(&:title)

vikunja.project(project.id).title
vikunja.project(project.id).update({id: project.id, title: "Renamed"})
vikunja.project(project.id).patch({title: "Patched"})
vikunja.project(project.id).delete
project = vikunja.project(1)

task = project.create_task({title: "write the client", priority: 3})

project.tasks(filter: "done = false", sort_by: ["due_date"]).map(&:title)
project.task_by_index(1).title

vikunja.task(task.id).title
vikunja.task(task.id).update({id: task.id, title: "write the client", done: true})
vikunja.task(task.id).patch({percent_done: 0.5})
vikunja.task(task.id).mark_read
vikunja.task(task.id).delete
vikunja.tasks(filter: "due_date > now", expand: "subtasks").each do |task|
  task.title
  task.due_date
  task.project_id
  task.done?
  task.labels.map(&:title)
  task.assignees.map(&:username)
end
task = vikunja.task(7)

comment = task.create_comment({comment: "shipped"})

comment.author.username
task.task_comments(order_by: "desc").map(&:comment)
task.comment(comment.id).update({id: comment.id, comment: "shipped twice"})
task.comment(comment.id).delete
label = vikunja.create_label({title: "urgent", hex_color: "e8384f"})

vikunja.task(7).add_label(label.id)
vikunja.task(7).task_labels.map(&:title)
vikunja.task(7).replace_labels([{id: label.id}])
vikunja.task(7).remove_label(label.id)

vikunja.labels.map(&:title)
vikunja.label(label.id).patch({title: "later"})
vikunja.label(label.id).delete
vikunja.task(7).add_assignee(2)
vikunja.task(7).task_assignees.map(&:username)
vikunja.task(7).replace_assignees([{id: 2}, {id: 3}])
vikunja.task(7).remove_assignee(2)
vikunja.task(7).add_relation({other_task_id: 8, relation_kind: "subtask"})
vikunja.task(7).remove_relation("subtask", 8)
vikunja.task(7).duplicate.duplicated_task.title
vikunja.task(7).reposition({project_view_id: 4, position: 2.5})
view = vikunja.project(1).view(4)

view.view_kind
view.tasks.map(&:title)
view.buckets.map(&:title)
view.buckets_with_tasks.items.flat_map{|bucket| bucket.tasks.map(&:title)}

bucket = view.create_bucket({title: "Doing", limit: 5})

view.move_task(bucket.id, {task_id: 7, project_view_id: view.id})
view.update_bucket(bucket.id, {id: bucket.id, title: "In progress"})
view.remove_bucket(bucket.id)
entry = vikunja.create_time_entry({task_id: 7, start_time: Time.now.utc.iso8601, comment: "pairing"})

vikunja.time_entries(filter: "start_time > now/w").map(&:comment)
vikunja.task(7).task_time_entries.map(&:id)
vikunja.project(1).time_entries.sum{|each| each.id}
vikunja.stop_timer.end_time
vikunja.time_entry(entry.id).delete
team = vikunja.create_team({name: "platform", is_public: false})

team.add_member({username: "tester"})
team.members.map(&:username)
team.toggle_admin("tester")
team.remove_member("tester")

vikunja.project(1).add_team({team_id: team.id, permission: 1})
vikunja.project(1).teams.map(&:name)
vikunja.project(1).remove_team(team.id)
share = vikunja.project(1).create_share({permission: 0, name: "read only"})

vikunja.project(1).shares.map(&:hash)
vikunja.authenticate_share(share.hash, {password: "secret"}).token
vikunja.project(1).remove_share(share.id)

vikunja.project(1).add_user({username: "tester", permission: 1})
vikunja.project(1).users.map(&:username)
vikunja.project(1).search_users("test").map(&:username)
vikunja.project(1).remove_user("tester")

Bytes go up as they are, so read a file in binary and say what it is. Several parts may share one field name.

result = vikunja.task(7).upload_attachments([
  {name: "files", filename: "notes.txt", content_type: "text/plain", body: File.binread("notes.txt")},
  {name: "files", filename: "diagram.png", content_type: "image/png", body: File.binread("diagram.png")},
])

result.success.map{|attachment| attachment.file.name}
result.errors.map(&:message)

An attachment knows its own address wherever it arrived, so it downloads and deletes itself.

stored = result.success.first

stored.download.bytes
stored.delete

vikunja.task(7).task_attachments.map{|attachment| attachment.file.size}
vikunja.task(7).attachments.map{|attachment| attachment.download.bytes.bytesize}

A download is the bytes the API answered with, not a document. It is fetched when it is first read, and the headers arrive with it.

file = vikunja.task(7).download_attachment(3)

file.media_type
file.bytes

vikunja.task(7).attachment(3).preview("sm").bytes
vikunja.task(7).attachment(3).delete

A file is as large as it is. Give a download a block and the body arrives a chunk at a time, so nothing holds the whole of it:

File.open("notes.txt", "wb") do |out|
  vikunja.task(7).download_attachment(3) do |response|
    response.body.each{|chunk| out.write(chunk)}
  end
end

Every download takes a block the same way: avatar, background, preview, totp_qrcode, notifications_atom and download_export.

An upload streams the other way. Hand a part an open file rather than its bytes, and it is read from disk as it goes on the wire:

vikunja.task(7).upload_attachments([
  {name: "files", filename: "recording.mp4", content_type: "video/mp4", body: File.open("recording.mp4", "rb")},
])
vikunja.project(1).upload_background({name: "background", filename: "wall.jpg", content_type: "image/jpeg", body: File.binread("wall.jpg")})

background = vikunja.project(1).background

background.media_type
background.bytes

vikunja.project(1).remove_background
filter = vikunja.create_filter({title: "overdue", filters: {filter: "due_date < now && done = false"}})

vikunja.filter(filter.id).filters.filter
vikunja.filter(filter.id).patch({title: "late"})
vikunja.filter(filter.id).delete
vikunja.notifications.map(&:name)
vikunja.update_notification(3, {id: 3, read: true})
vikunja.mark_notifications_read.message
vikunja.clear_notifications

vikunja.subscribe("projects", 1).entity
vikunja.unsubscribe("projects", 1)

vikunja.add_reaction("tasks", 7, {value: "👍"})
vikunja.reactions("tasks", 7).value
vikunja.remove_reaction("tasks", 7, {value: "👍"})
webhook = vikunja.project(1).create_webhook({target_url: "https://example.com/hook", events: ["task.created"]})

vikunja.webhook_events.value
vikunja.project(1).webhooks.map(&:target_url)
vikunja.project(1).update_webhook(webhook.id, {id: webhook.id, events: ["task.updated"]})
vikunja.project(1).remove_webhook(webhook.id)
token = vikunja.create_token({
  title: "integration",
  expires_at: (Time.now.utc + 86400).iso8601,
  permissions: {projects: ["read_all", "create"]},
})

token.token
vikunja.tokens.map(&:title)
vikunja.delete_token(token.id)

vikunja.caldav_tokens.map(&:created)
vikunja.create_caldav_token.token
vikunja.user.settings.timezone
vikunja.update_settings({name: "Tester", week_start: 1, timezone: "Europe/London"}).message
vikunja.upload_avatar({name: "avatar", filename: "me.png", content_type: "image/png", body: File.binread("me.png")})
vikunja.avatar("tester", size: 64).bytes
vikunja.set_avatar_provider(avatar_provider: "initials")

vikunja.enroll_totp.url
vikunja.enable_totp(passcode: "123456")
vikunja.totp_qrcode.bytes
vikunja.disable_totp(password: "secret")

vikunja.sessions.map(&:device_info)
vikunja.delete_session("abc")
vikunja.users("test").map(&:username)
vikunja.timezones.value
vikunja.health.status
vikunja.routes.value
vikunja.notifications_atom.bytes
bot = vikunja.create_bot({username: "ci-bot", name: "CI"})

vikunja.bots.map(&:username)
vikunja.bot(bot.id).patch({name: "CI runner"})
vikunja.bot(bot.id).delete
overview = vikunja.admin_overview

overview.users
overview.projects
overview.shares.link_shares
overview.license.licensed

vikunja.admin_users(q: "test").map(&:username)
vikunja.create_admin_user({username: "new", email: "new@example.com", password: "secret"})
vikunja.set_admin(1, {is_admin: true})
vikunja.set_user_status(1, {status: 0})
vikunja.set_user_password(1, {new_password: "another"})
vikunja.send_password_reset_email(1)
vikunja.remove_admin_user(1, mode: "soft")

vikunja.admin_projects(q: "inbox").map(&:title)
vikunja.set_project_owner(1, {owner_id: 2})
case vikunja.project(1)
in {title: String => title, owner: {username: String => owner}}
  [title, owner]
end

case vikunja.task(7)
in {done: true, title:}
  "#{title} is done"
in {done: false, due_date: String => due}
  "#{title} is due #{due}"
end
begin
  vikunja.create_project({})
rescue Async::Vikunja::Failure => error
  error.status
  error.code
  error.title
  error.detail
  error.errors.map{|each| each[:message]}
  error.retry_after
end

begin
  vikunja.project(404).title
rescue Async::REST::ResponseError => error
  error.response.status
rescue ::JSON::ParserError => error
  error.message
end
project = vikunja.project(1)

project.title
project = vikunja.project(1) unless project.cacheable?
project.title
token = vikunja.create_token({title: "ci"})

token.inspect
token.value[:token]
Async::Vikunja::Client.open(url) do |client|
  vikunja = client.authenticated(ENV.fetch("VIKUNJA_TOKEN"))

  tasks = vikunja.projects.map do |project|
    Async do
      project.tasks.count
    end
  end

  tasks.sum(&:wait)
end