async-vikunja 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +362 -0
  3. data/lib/async/vikunja/admin.rb +97 -0
  4. data/lib/async/vikunja/assignees.rb +20 -0
  5. data/lib/async/vikunja/attachments.rb +51 -0
  6. data/lib/async/vikunja/auth.rb +52 -0
  7. data/lib/async/vikunja/bots.rb +43 -0
  8. data/lib/async/vikunja/buckets.rb +51 -0
  9. data/lib/async/vikunja/client.rb +76 -0
  10. data/lib/async/vikunja/comments.rb +31 -0
  11. data/lib/async/vikunja/error.rb +52 -0
  12. data/lib/async/vikunja/escape.rb +9 -0
  13. data/lib/async/vikunja/filters.rb +39 -0
  14. data/lib/async/vikunja/labels.rb +71 -0
  15. data/lib/async/vikunja/migration.rb +89 -0
  16. data/lib/async/vikunja/notifications.rb +45 -0
  17. data/lib/async/vikunja/page.rb +112 -0
  18. data/lib/async/vikunja/projects.rb +133 -0
  19. data/lib/async/vikunja/reactions.rb +19 -0
  20. data/lib/async/vikunja/redacted.rb +37 -0
  21. data/lib/async/vikunja/representation.rb +73 -0
  22. data/lib/async/vikunja/service.rb +137 -0
  23. data/lib/async/vikunja/sessions.rb +29 -0
  24. data/lib/async/vikunja/settings.rb +67 -0
  25. data/lib/async/vikunja/sharing.rb +131 -0
  26. data/lib/async/vikunja/submit.rb +97 -0
  27. data/lib/async/vikunja/subscriptions.rb +21 -0
  28. data/lib/async/vikunja/tasks.rb +207 -0
  29. data/lib/async/vikunja/teams.rb +85 -0
  30. data/lib/async/vikunja/time_entries.rb +55 -0
  31. data/lib/async/vikunja/tokens.rb +59 -0
  32. data/lib/async/vikunja/users.rb +107 -0
  33. data/lib/async/vikunja/version.rb +10 -0
  34. data/lib/async/vikunja/views.rb +75 -0
  35. data/lib/async/vikunja/webhooks.rb +55 -0
  36. data/lib/async/vikunja/wrapper.rb +75 -0
  37. data/lib/async/vikunja.rb +2 -0
  38. data/license.md +21 -0
  39. metadata +105 -0
@@ -0,0 +1,76 @@
1
+ require "async/rest/resource"
2
+
3
+ require_relative "admin"
4
+ require_relative "assignees"
5
+ require_relative "attachments"
6
+ require_relative "auth"
7
+ require_relative "bots"
8
+ require_relative "buckets"
9
+ require_relative "comments"
10
+ require_relative "error"
11
+ require_relative "filters"
12
+ require_relative "labels"
13
+ require_relative "migration"
14
+ require_relative "notifications"
15
+ require_relative "page"
16
+ require_relative "projects"
17
+ require_relative "reactions"
18
+ require_relative "service"
19
+ require_relative "sessions"
20
+ require_relative "settings"
21
+ require_relative "sharing"
22
+ require_relative "subscriptions"
23
+ require_relative "submit"
24
+ require_relative "tasks"
25
+ require_relative "teams"
26
+ require_relative "time_entries"
27
+ require_relative "tokens"
28
+ require_relative "users"
29
+ require_relative "version"
30
+ require_relative "views"
31
+ require_relative "webhooks"
32
+
33
+ module Async
34
+ module Vikunja
35
+ class Client < Async::REST::Resource
36
+ ENDPOINT = Async::HTTP::Endpoint.parse(ENV.fetch("VIKUNJA_URL", "http://localhost:3456/api/v2/"))
37
+
38
+ USER_AGENT = "async-vikunja (https://github.com/n-at-han-k/async-vikunja, v" + VERSION + ")"
39
+
40
+ include Submit
41
+
42
+ include Admin
43
+ include Auth
44
+ include Bots
45
+ include Filters
46
+ include Labels
47
+ include Migration
48
+ include Notifications
49
+ include Projects
50
+ include Reactions
51
+ include Service
52
+ include Sessions
53
+ include Settings
54
+ include Sharing
55
+ include Subscriptions
56
+ include Tasks
57
+ include Teams
58
+ include TimeEntries
59
+ include Tokens
60
+ include Users
61
+ include Webhooks
62
+
63
+ def initialize(delegate, reference = ::Protocol::URL::Reference.parse, headers = ::Protocol::HTTP::Headers.new)
64
+ super
65
+
66
+ unless @headers["user-agent"]
67
+ @headers = @headers.merge("user-agent" => USER_AGENT)
68
+ end
69
+ end
70
+
71
+ def authenticated(token)
72
+ self.with(headers: {"authorization" => "Bearer " + token})
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,31 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Vikunja
5
+ class TaskComment < Representation
6
+ def id = self.value[:id]
7
+
8
+ def comment = self.value[:comment]
9
+
10
+ def created = self.value[:created]
11
+
12
+ def updated = self.value[:updated]
13
+
14
+ def reactions = self.value[:reactions]
15
+
16
+ def author = self.nested(User, self.value[:author])
17
+
18
+ def max_permission = self.value[:max_permission]
19
+
20
+ def update(payload) = self.submit(TaskComment, @resource, payload, method: :put)
21
+
22
+ def patch(payload) = self.merge_patch(TaskComment, @resource, payload)
23
+
24
+ def delete = self.discard(@resource)
25
+ end
26
+
27
+ class TaskCommentPage < Page
28
+ MEMBER = TaskComment
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,52 @@
1
+ module Async
2
+ module Vikunja
3
+ class Error < StandardError
4
+ end
5
+
6
+ class Failure < Error
7
+ def self.for(response)
8
+ problem = self.problem_for(response.read)
9
+
10
+ self.new(
11
+ response.status.to_s + ": " + self.summary_of(problem),
12
+ status: response.status,
13
+ problem: problem,
14
+ retry_after: response.headers["retry-after"],
15
+ )
16
+ end
17
+
18
+ def self.problem_for(payload)
19
+ case payload
20
+ in Hash => problem
21
+ problem
22
+ else
23
+ {detail: payload.to_s}
24
+ end
25
+ end
26
+
27
+ def self.summary_of(problem)
28
+ problem[:title] || problem[:detail] || problem.inspect
29
+ end
30
+
31
+ def initialize(message, status:, problem: {}, retry_after: nil)
32
+ super(message)
33
+
34
+ @status = status
35
+ @problem = problem
36
+ @retry_after = retry_after
37
+ end
38
+
39
+ attr :status
40
+ attr :problem
41
+ attr :retry_after
42
+
43
+ def code = @problem[:code]
44
+
45
+ def title = @problem[:title]
46
+
47
+ def detail = @problem[:detail]
48
+
49
+ def errors = Array(@problem[:errors])
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,9 @@
1
+ require "protocol/url/encoding"
2
+
3
+ module Async
4
+ module Vikunja
5
+ def self.escape(value)
6
+ ::Protocol::URL::Encoding.escape(value.to_s)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,39 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Vikunja
5
+ class SavedFilter < Representation
6
+ def id = self.value[:id]
7
+
8
+ def title = self.value[:title]
9
+
10
+ def description = self.value[:description]
11
+
12
+ def is_favorite = self.value[:is_favorite]
13
+
14
+ def max_permission = self.value[:max_permission]
15
+
16
+ def created = self.value[:created]
17
+
18
+ def updated = self.value[:updated]
19
+
20
+ def owner = self.nested(User, self.value[:owner])
21
+
22
+ def filters = self.nested(TaskCollection, self.value[:filters])
23
+
24
+ def favorite? = self.is_favorite == true
25
+
26
+ def update(payload) = self.submit(SavedFilter, @resource, payload, method: :put)
27
+
28
+ def patch(payload) = self.merge_patch(SavedFilter, @resource, payload)
29
+
30
+ def delete = self.discard(@resource)
31
+ end
32
+
33
+ module Filters
34
+ def filter(id) = SavedFilter.new(self.with(path: "filters/" + Vikunja.escape(id)))
35
+
36
+ def create_filter(payload) = self.submit(SavedFilter, self.with(path: "filters"), payload, identify: true)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,71 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Vikunja
5
+ class Label < Representation
6
+ def id = self.value[:id]
7
+
8
+ def title = self.value[:title]
9
+
10
+ def description = self.value[:description]
11
+
12
+ def hex_color = self.value[:hex_color]
13
+
14
+ def created = self.value[:created]
15
+
16
+ def updated = self.value[:updated]
17
+
18
+ def created_by = self.nested(User, self.value[:created_by])
19
+
20
+ def max_permission = self.value[:max_permission]
21
+
22
+ def update(payload) = self.submit(Label, @resource, payload, method: :put)
23
+
24
+ def patch(payload) = self.merge_patch(Label, @resource, payload)
25
+
26
+ def delete = self.discard(@resource)
27
+ end
28
+
29
+ class LabelWithTaskID < Representation
30
+ def id = self.value[:id]
31
+
32
+ def title = self.value[:title]
33
+
34
+ def description = self.value[:description]
35
+
36
+ def hex_color = self.value[:hex_color]
37
+
38
+ def created = self.value[:created]
39
+
40
+ def updated = self.value[:updated]
41
+
42
+ def created_by = self.nested(User, self.value[:created_by])
43
+ end
44
+
45
+ class LabelTask < Representation
46
+ def label_id = self.value[:label_id]
47
+
48
+ def created = self.value[:created]
49
+ end
50
+
51
+ class LabelTaskBulk < Representation
52
+ def labels = self.nested_list(Label, self.value[:labels])
53
+ end
54
+
55
+ class LabelPage < Page
56
+ MEMBER = LabelWithTaskID
57
+ end
58
+
59
+ class TaskLabelPage < Page
60
+ MEMBER = LabelWithTaskID
61
+ end
62
+
63
+ module Labels
64
+ def labels(**parameters) = Collection.for(self, "labels", LabelPage, **parameters)
65
+
66
+ def label(id) = Label.new(self.with(path: "labels/" + Vikunja.escape(id)))
67
+
68
+ def create_label(payload) = self.submit(Label, self.with(path: "labels"), payload, identify: true)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,89 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Vikunja
5
+ class ColumnMapping < Representation
6
+ def attribute = self.value[:attribute]
7
+
8
+ def column_index = self.value[:column_index]
9
+
10
+ def column_name = self.value[:column_name]
11
+ end
12
+
13
+ class DetectionResult < Representation
14
+ def columns = self.value[:columns]
15
+
16
+ def date_format = self.value[:date_format]
17
+
18
+ def delimiter = self.value[:delimiter]
19
+
20
+ def preview_rows = self.value[:preview_rows]
21
+
22
+ def quote_char = self.value[:quote_char]
23
+
24
+ def suggested_mapping = self.nested_list(ColumnMapping, self.value[:suggested_mapping])
25
+ end
26
+
27
+ class PreviewTask < Representation
28
+ def title = self.value[:title]
29
+
30
+ def description = self.value[:description]
31
+
32
+ def done = self.value[:done]
33
+
34
+ def due_date = self.value[:due_date]
35
+
36
+ def start_date = self.value[:start_date]
37
+
38
+ def end_date = self.value[:end_date]
39
+
40
+ def labels = self.value[:labels]
41
+
42
+ def priority = self.value[:priority]
43
+
44
+ def project = self.value[:project]
45
+ end
46
+
47
+ class PreviewResult < Representation
48
+ def tasks = self.nested_list(PreviewTask, self.value[:tasks])
49
+
50
+ def total_rows = self.value[:total_rows]
51
+ end
52
+
53
+ class Status < Representation
54
+ def id = self.value[:id]
55
+
56
+ def migrator_name = self.value[:migrator_name]
57
+
58
+ def started_at = self.value[:started_at]
59
+
60
+ def finished_at = self.value[:finished_at]
61
+ end
62
+
63
+ module Migration
64
+ def csv_detect(part) = self.upload(DetectionResult, self.with(path: "migration/csv/detect"), [part])
65
+
66
+ def csv_preview(parts) = self.upload(PreviewResult, self.with(path: "migration/csv/preview"), parts)
67
+
68
+ def csv_migrate(parts) = self.upload(Message, self.with(path: "migration/csv/migrate"), parts)
69
+
70
+ def csv_status = Status.new(self.with(path: "migration/csv/status"))
71
+
72
+ def planka_migrate(payload) = self.submit(Message, self.with(path: "migration/planka/migrate"), payload)
73
+
74
+ def planka_status = Status.new(self.with(path: "migration/planka/status"))
75
+
76
+ def ticktick_migrate(part) = self.upload(Message, self.with(path: "migration/ticktick/migrate"), [part])
77
+
78
+ def ticktick_status = Status.new(self.with(path: "migration/ticktick/status"))
79
+
80
+ def vikunja_file_migrate(part) = self.upload(Message, self.with(path: "migration/vikunja-file/migrate"), [part])
81
+
82
+ def vikunja_file_status = Status.new(self.with(path: "migration/vikunja-file/status"))
83
+
84
+ def wekan_migrate(part) = self.upload(Message, self.with(path: "migration/wekan/migrate"), [part])
85
+
86
+ def wekan_status = Status.new(self.with(path: "migration/wekan/status"))
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Vikunja
5
+ class DatabaseNotification < Representation
6
+ def id = self.value[:id]
7
+
8
+ def name = self.value[:name]
9
+
10
+ def notification = self.value[:notification]
11
+
12
+ def read_at = self.value[:read_at]
13
+
14
+ def created = self.value[:created]
15
+ end
16
+
17
+ class DatabaseNotifications < Representation
18
+ def id = self.value[:id]
19
+
20
+ def name = self.value[:name]
21
+
22
+ def notification = self.value[:notification]
23
+
24
+ def read = self.value[:read]
25
+
26
+ def read_at = self.value[:read_at]
27
+
28
+ def created = self.value[:created]
29
+ end
30
+
31
+ class NotificationPage < Page
32
+ MEMBER = DatabaseNotification
33
+ end
34
+
35
+ module Notifications
36
+ def notifications(**parameters) = Collection.for(self, "notifications", NotificationPage, **parameters)
37
+
38
+ def mark_notifications_read = self.submit(Message, self.with(path: "notifications"), nil)
39
+
40
+ def clear_notifications = self.discard(self.with(path: "notifications"))
41
+
42
+ def update_notification(id, payload) = self.submit(DatabaseNotifications, self.with(path: "notifications/" + Vikunja.escape(id)), payload, method: :put)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,112 @@
1
+ require_relative "representation"
2
+
3
+ module Async
4
+ module Vikunja
5
+ class Page < Representation
6
+ include Enumerable
7
+
8
+ MEMBER = Representation
9
+
10
+ def items = Array(self.value[:items])
11
+
12
+ def page = self.value[:page].to_i
13
+
14
+ def per_page = self.value[:per_page].to_i
15
+
16
+ def total = self.value[:total].to_i
17
+
18
+ def total_pages = self.value[:total_pages].to_i
19
+
20
+ def last? = self.page >= [self.total_pages, 1].max
21
+
22
+ def empty? = self.items.empty?
23
+
24
+ def each(&block)
25
+ if block
26
+ self.items.each do |record|
27
+ block.call(self.member_for(record))
28
+ end
29
+ else
30
+ self.to_enum(:each)
31
+ end
32
+ end
33
+
34
+ def member_class = self.class::MEMBER
35
+
36
+ def member_path(record) = Vikunja.escape(record[:id])
37
+
38
+ def member_resource(record)
39
+ @resource.with(path: self.member_path(record), parameters: nil, merge: false)
40
+ end
41
+
42
+ def member_for(record)
43
+ self.member_class.new(self.member_resource(record), value: record, metadata: self.metadata)
44
+ end
45
+ end
46
+
47
+ class Collection
48
+ include Enumerable
49
+
50
+ DEFAULT_PER_PAGE = 50
51
+
52
+ def self.for(parent, path, page_class, **parameters)
53
+ self.new(parent.with(path: path, parameters: parameters), page_class)
54
+ end
55
+
56
+ def initialize(resource, page_class)
57
+ @resource = resource
58
+ @page_class = page_class
59
+ end
60
+
61
+ attr :resource
62
+
63
+ attr :page_class
64
+
65
+ def each_page(per_page: DEFAULT_PER_PAGE, &block)
66
+ if block
67
+ self.walk(per_page, &block)
68
+ else
69
+ self.to_enum(:each_page, per_page: per_page)
70
+ end
71
+ end
72
+
73
+ def each(**options, &block)
74
+ if block
75
+ self.each_page(**options) do |page|
76
+ page.each(&block)
77
+ end
78
+ else
79
+ self.to_enum(:each, **options)
80
+ end
81
+ end
82
+
83
+ def page(number, per_page: DEFAULT_PER_PAGE)
84
+ @page_class.new(@resource.with(parameters: {page: number, per_page: per_page}))
85
+ end
86
+
87
+ def total
88
+ self.page(1, per_page: 1).total
89
+ end
90
+
91
+ def walk(per_page, &block)
92
+ number = 1
93
+
94
+ while true
95
+ page = self.page(number, per_page: per_page)
96
+
97
+ if page.empty?
98
+ break
99
+ end
100
+
101
+ block.call(page)
102
+
103
+ if page.last?
104
+ break
105
+ end
106
+
107
+ number += 1
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,133 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Vikunja
5
+ class Project < Representation
6
+ def id = self.value[:id]
7
+
8
+ def title = self.value[:title]
9
+
10
+ def description = self.value[:description]
11
+
12
+ def identifier = self.value[:identifier]
13
+
14
+ def hex_color = self.value[:hex_color]
15
+
16
+ def position = self.value[:position]
17
+
18
+ def parent_project_id = self.value[:parent_project_id]
19
+
20
+ def is_archived = self.value[:is_archived]
21
+
22
+ def is_favorite = self.value[:is_favorite]
23
+
24
+ def max_permission = self.value[:max_permission]
25
+
26
+ def background_blur_hash = self.value[:background_blur_hash]
27
+
28
+ def background_information = self.value[:background_information]
29
+
30
+ def created = self.value[:created]
31
+
32
+ def updated = self.value[:updated]
33
+
34
+ def owner = self.nested(User, self.value[:owner])
35
+
36
+ def subscription = self.nested(Subscription, self.value[:subscription])
37
+
38
+ def views = self.nested_list(ProjectView, self.value[:views])
39
+
40
+ def archived? = self.is_archived == true
41
+
42
+ def favorite? = self.is_favorite == true
43
+
44
+ def tasks(**parameters) = Collection.for(@resource, "tasks", ProjectTaskPage, **parameters)
45
+
46
+ def project_views(**parameters) = Collection.for(@resource, "views", ProjectViewPage, **parameters)
47
+
48
+ def shares(**parameters) = Collection.for(@resource, "shares", LinkSharingPage, **parameters)
49
+
50
+ def teams(**parameters) = Collection.for(@resource, "teams", TeamWithPermissionPage, **parameters)
51
+
52
+ def users(**parameters) = Collection.for(@resource, "users", ProjectUserPage, **parameters)
53
+
54
+ def webhooks(**parameters) = Collection.for(@resource, "webhooks", ProjectWebhookPage, **parameters)
55
+
56
+ def time_entries(**parameters) = Collection.for(@resource, "time-entries", ProjectTimeEntryPage, **parameters)
57
+
58
+ def view(id) = ProjectView.new(@resource.with(path: "views/" + Vikunja.escape(id)))
59
+
60
+ def share(id) = LinkSharing.new(@resource.with(path: "shares/" + Vikunja.escape(id)))
61
+
62
+ def task_by_index(index) = TaskReadOneBody.new(@resource.with(path: "tasks/by-index/" + Vikunja.escape(index)))
63
+
64
+ def background = Download.new(@resource.with(path: "background"))
65
+
66
+ def search_users(query) = UserSearchPage.new(@resource.with(path: "users/search", parameters: {q: query}))
67
+
68
+ def create_task(payload) = self.submit(Task, @resource.with(path: "tasks"), payload, identify: "../../../tasks/")
69
+
70
+ def create_tasks(titles) = self.submit(BulkTaskCreation, @resource.with(path: "tasks/bulk"), {tasks: titles}, identify: nil)
71
+
72
+ def create_view(payload) = self.submit(ProjectView, @resource.with(path: "views"), payload, identify: true)
73
+
74
+ def create_share(payload) = self.submit(LinkSharing, @resource.with(path: "shares"), payload, identify: true)
75
+
76
+ def create_webhook(payload) = self.submit(Webhook, @resource.with(path: "webhooks"), payload, identify: true)
77
+
78
+ def add_team(payload) = self.submit(TeamProject, @resource.with(path: "teams"), payload)
79
+
80
+ def add_user(payload) = self.submit(ProjectUser, @resource.with(path: "users"), payload)
81
+
82
+ def update_team(team, payload) = self.submit(TeamProject, @resource.with(path: "teams/" + Vikunja.escape(team)), payload, method: :put)
83
+
84
+ def update_user(user, payload) = self.submit(ProjectUser, @resource.with(path: "users/" + Vikunja.escape(user)), payload, method: :put)
85
+
86
+ def update_webhook(webhook, payload) = self.submit(Webhook, @resource.with(path: "webhooks/" + Vikunja.escape(webhook)), payload, method: :put)
87
+
88
+ def remove_team(team) = self.discard(@resource.with(path: "teams/" + Vikunja.escape(team)))
89
+
90
+ def remove_user(user) = self.discard(@resource.with(path: "users/" + Vikunja.escape(user)))
91
+
92
+ def remove_webhook(webhook) = self.discard(@resource.with(path: "webhooks/" + Vikunja.escape(webhook)))
93
+
94
+ def remove_share(id) = self.discard(@resource.with(path: "shares/" + Vikunja.escape(id)))
95
+
96
+ def duplicate(payload = {}) = self.submit(ProjectDuplicate, @resource.with(path: "duplicate"), payload)
97
+
98
+ def upload_background(part) = self.upload(Project, @resource.with(path: "backgrounds/upload"), [part], method: :put)
99
+
100
+ def remove_background = self.submit(Project, @resource.with(path: "background"), nil, method: :delete)
101
+
102
+ def update(payload) = self.submit(Project, @resource, payload, method: :put)
103
+
104
+ def patch(payload) = self.merge_patch(Project, @resource, payload)
105
+
106
+ def delete = self.discard(@resource)
107
+ end
108
+
109
+ class ProjectDuplicate < Representation
110
+ def parent_project_id = self.value[:parent_project_id]
111
+
112
+ def duplicate_shares = self.value[:duplicate_shares]
113
+
114
+ def duplicated_project = self.nested(Project, self.value[:duplicated_project])
115
+ end
116
+
117
+ class ProjectPage < Page
118
+ MEMBER = Project
119
+ end
120
+
121
+ class AdminProjectPage < ProjectPage
122
+ def member_path(record) = "../../projects/" + Vikunja.escape(record[:id])
123
+ end
124
+
125
+ module Projects
126
+ def projects(**parameters) = Collection.for(self, "projects", ProjectPage, **parameters)
127
+
128
+ def project(id) = Project.new(self.with(path: "projects/" + Vikunja.escape(id)))
129
+
130
+ def create_project(payload) = self.submit(Project, self.with(path: "projects"), payload, identify: true)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Vikunja
5
+ class Reaction < Representation
6
+ def created = self.value[:created]
7
+
8
+ def user = self.nested(User, self.value[:user])
9
+ end
10
+
11
+ module Reactions
12
+ def reactions(entitykind, entityid) = Representation.new(self.with(path: Vikunja.escape(entitykind) + "/" + Vikunja.escape(entityid) + "/reactions"))
13
+
14
+ def add_reaction(entitykind, entityid, payload) = self.submit(Reaction, self.with(path: Vikunja.escape(entitykind) + "/" + Vikunja.escape(entityid) + "/reactions"), payload)
15
+
16
+ def remove_reaction(entitykind, entityid, payload) = self.submit(Representation, self.with(path: Vikunja.escape(entitykind) + "/" + Vikunja.escape(entityid) + "/reactions/delete"), payload)
17
+ end
18
+ end
19
+ end