nin 0.0.0 → 1.3.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.
@@ -0,0 +1,20 @@
1
+ class Date
2
+ def self.parse_or_return(unparsed_date)
3
+ return unparsed_date unless unparsed_date.is_a?(String)
4
+
5
+ Date.parse(unparsed_date)
6
+ end
7
+
8
+ def humanize
9
+ case self
10
+ when Date.today.prev_day
11
+ 'yesterday'
12
+ when Date.today
13
+ 'today'
14
+ when Date.today.succ
15
+ 'tomorrow'
16
+ else
17
+ self.to_s
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ class Integer
2
+ def length
3
+ Math.log10(self).to_i + 1
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class NilClass
2
+ def ensure_array
3
+ self.to_a
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Object
2
+ def ensure_array
3
+ [self]
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class String
2
+ def remove_color
3
+ self.gsub(/\e\[([;\d]+)?m/, '')
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module Nin
2
+ module Integration
3
+ class Service
4
+ def initialize(client)
5
+ @client = client
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,94 @@
1
+ module Nin
2
+ module Integration
3
+ class Service
4
+ class Todoist < Service
5
+ def projects
6
+ @projects ||= Project.new(@client)
7
+ end
8
+
9
+ def items
10
+ @items ||= Item.new(@client)
11
+ end
12
+
13
+ class Project < Service
14
+ def all
15
+ @client
16
+ .sync
17
+ .read_resources(['projects'])
18
+ .fetch('projects').reduce({}) { |projects, p| projects.update(p["id"] => p["name"]) }
19
+ end
20
+
21
+ def add(project)
22
+ commands = [
23
+ {
24
+ "type": "project_add",
25
+ "temp_id": SecureRandom.uuid,
26
+ "uuid": SecureRandom.uuid,
27
+ "args": project
28
+ }
29
+ ].to_json
30
+
31
+ @client.sync.write_resources(commands).fetch('temp_id_mapping').values.first
32
+ end
33
+ end
34
+
35
+ class Item < Service
36
+ def all
37
+ data = @client.sync.read_resources(['projects', 'items'])
38
+
39
+ projects = data.fetch('projects').reduce({}) do |projects, p|
40
+ projects.update(p["id"] => p["name"])
41
+ end
42
+
43
+ data.fetch('items').reduce([]) do |tasks, t|
44
+ tasks << [
45
+ t["content"],
46
+ (t["due"] || {}).fetch('date', nil),
47
+ projects.fetch(t["project_id"]),
48
+ t["id"],
49
+ (t["checked"].to_i == 1)
50
+ ]
51
+ end
52
+ end
53
+
54
+ def add(item)
55
+ commands = [
56
+ {
57
+ "type": "item_add",
58
+ "temp_id": SecureRandom.uuid,
59
+ "uuid": SecureRandom.uuid,
60
+ "args": item
61
+ }
62
+ ].to_json
63
+
64
+ @client.sync.write_resources(commands).fetch('temp_id_mapping').values.first
65
+ end
66
+
67
+ def update(items)
68
+ commands = items.ensure_array.map do |item|
69
+ {
70
+ "type": "item_update",
71
+ "uuid": SecureRandom.uuid,
72
+ "args": item
73
+ }
74
+ end.to_json
75
+
76
+ @client.sync.write_resources(commands)
77
+ end
78
+
79
+ def delete(ids)
80
+ commands = ids.ensure_array.map do |id|
81
+ {
82
+ "type": "item_delete",
83
+ "uuid": SecureRandom.uuid,
84
+ "args": { 'id': id }
85
+ }
86
+ end.to_json
87
+
88
+ @client.sync.write_resources(commands)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,12 @@
1
+ module Nin
2
+ module Integration
3
+ class Synchronizer
4
+ attr_reader :timeout_interval
5
+
6
+ def initialize(client, timeout_interval = nil)
7
+ @client = client
8
+ @timeout_interval = timeout_interval
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,112 @@
1
+ module Nin
2
+ module Integration
3
+ class Synchronizer
4
+ class Todoist < Synchronizer
5
+ def sync(op, params = {})
6
+ @service = Integration::Service::Todoist.new(@client)
7
+
8
+ case op
9
+ when :read
10
+ sync_read(params)
11
+ when :add
12
+ sync_add(params)
13
+ when :edit
14
+ sync_edit(params)
15
+ when :edit_completed
16
+ sync_edit(params, :checked)
17
+ when :delete
18
+ sync_delete(params)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def sync_read(params)
25
+ items = params.fetch(:items)
26
+ next_id = params.fetch(:next_id)
27
+
28
+ id = next_id
29
+ synced_uids = @service.items.all.map do |t|
30
+ item = Item.new(id, *t)
31
+
32
+ if existing_item = items.find_by(:uid, item.uid)
33
+ existing_item.edit(item.desc, item.date, item.tags, item.completed)
34
+ else
35
+ items << item
36
+ id += 1
37
+ end
38
+
39
+ item.uid
40
+ end
41
+
42
+ unsynced_uids = items.where(:uid) { |item_uid| !item_uid.nil? }.map(&:uid) - synced_uids
43
+ unsynced_uids.each do |uid|
44
+ item = items.find_by(:uid, uid)
45
+ t = @client.items.get(uid)
46
+
47
+ items.delete(item) and next if t.nil? || t.fetch("is_deleted") == 1
48
+ item.completed = (t.fetch("checked") == 1)
49
+ end
50
+ end
51
+
52
+ def sync_add(params)
53
+ item = params.fetch(:item)
54
+
55
+ payload = _get_item_write_payload(item)
56
+
57
+ uid = @service.items.add(payload)
58
+ item.uid = uid
59
+ end
60
+
61
+ def sync_edit(params, type = :full)
62
+ payload = params.fetch(:items).ensure_array.map do |item|
63
+ item_payload = if type == :checked
64
+ { checked: item.completed }
65
+ else
66
+ _get_item_write_payload(item)
67
+ end
68
+ item_payload[:id] = item.uid
69
+
70
+ item_payload
71
+ end
72
+
73
+ @service.items.update(payload)
74
+ end
75
+
76
+ def sync_delete(params)
77
+ @service.items.delete(params.fetch(:ids))
78
+ end
79
+
80
+ def _get_item_write_payload(item)
81
+ payload = {
82
+ content: item.desc,
83
+ due: { date: item.date },
84
+ checked: item.completed
85
+ }
86
+
87
+ if project_name = item.tags.first
88
+ @projects ||= @service.projects.all
89
+ @project_names ||= @projects.values
90
+
91
+ _new_project = false
92
+ project_id = unless @project_names.include?(project_name)
93
+ @service.projects.add(name: project_name)
94
+ _new_project = true
95
+ else
96
+ @projects.find { |k, v| v == project_name }.first
97
+ end
98
+
99
+ if _new_project
100
+ @projects[project_id] = project_name
101
+ @project_names.push(project_name)
102
+ end
103
+
104
+ payload[:project_id] = project_id
105
+ end
106
+
107
+ payload
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,5 @@
1
+ module Nin
2
+ module Integration
3
+ module Todoist; end;
4
+ end
5
+ end
@@ -0,0 +1,25 @@
1
+ module Nin
2
+ module Integration
3
+ module Todoist
4
+ class Client
5
+ BASE_URI = 'https://api.todoist.com/sync/v8'.freeze
6
+
7
+ def initialize(token)
8
+ @token = token
9
+ end
10
+
11
+ def sync
12
+ @sync ||= Client::Sync.new(@token)
13
+ end
14
+
15
+ def projects
16
+ @projects ||= Client::Project.new(@token)
17
+ end
18
+
19
+ def items
20
+ @items ||= Client::Item.new(@token)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ module Nin
2
+ module Integration
3
+ module Todoist
4
+ class Client
5
+ class BaseClient
6
+ def initialize(token)
7
+ @token = token
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ module Nin
2
+ module Integration
3
+ module Todoist
4
+ class Client
5
+ class Item < BaseClient
6
+ API_URI = "#{BASE_URI}/items".freeze
7
+
8
+ def get(id)
9
+ res = HTTP.headers(accept: "application/json")
10
+ .get("#{API_URI}/get", params: { token: @token, item_id: id })
11
+
12
+ JSON.parse(res.body.to_s)['item']
13
+ end
14
+
15
+ def add(item)
16
+ res = HTTP.headers(accept: "application/json")
17
+ .get("#{API_URI}/add", params: { token: @token, **item })
18
+
19
+ JSON.parse(res.body.to_s)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ module Nin
2
+ module Integration
3
+ module Todoist
4
+ class Client
5
+ class Project < BaseClient
6
+ API_URI = "#{BASE_URI}/projects".freeze
7
+
8
+ def get(id)
9
+ res = HTTP.headers(accept: "application/json")
10
+ .get("#{API_URI}/get", params: { token: @token, project_id: id })
11
+
12
+ JSON.parse(res.body.to_s)['project']
13
+ end
14
+ #
15
+ # def add(project)
16
+ # res = HTTP.headers(accept: "application/json")
17
+ # .get("#{API_URI}/add", params: { token: @token, **project })
18
+ #
19
+ # JSON.parse(res.body.to_s)
20
+ # end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ module Nin
2
+ module Integration
3
+ module Todoist
4
+ class Client
5
+ class Sync < BaseClient
6
+ API_URI = "#{BASE_URI}/sync".freeze
7
+
8
+ def read_resources(resource_types = ['all'], sync_token = '*')
9
+ res = HTTP.headers(accept: "application/json")
10
+ .get("#{BASE_URI}/sync", params: { token: @token,
11
+ sync_token: sync_token,
12
+ resource_types: resource_types.to_json })
13
+
14
+ data = JSON.parse(res.body.to_s)
15
+ unless resource_types == ['all']
16
+ data.slice(*resource_types)
17
+ else
18
+ data
19
+ end
20
+ end
21
+
22
+ def write_resources(commands)
23
+ res = HTTP.headers(accept: "application/json")
24
+ .get("#{BASE_URI}/sync", params: { token: @token,
25
+ commands: commands })
26
+
27
+ JSON.parse(res.body.to_s)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,70 @@
1
+ module Nin
2
+ class Item
3
+ attr_accessor :id, :desc, :date, :tags, :uid, :completed, :archived
4
+
5
+ def initialize(id,
6
+ desc,
7
+ date = Date.today,
8
+ tags = [],
9
+ uid = nil,
10
+ completed = false,
11
+ archived = false,
12
+ formatter = Presenter::ItemPresenter)
13
+
14
+ @id = id
15
+ @desc = desc
16
+ @date = Date.parse_or_return(date) || Date.today
17
+ @tags = tags.ensure_array
18
+ @uid = uid
19
+ @completed = completed
20
+ @archived = archived
21
+ @formatter = formatter.new(self)
22
+ end
23
+
24
+ def edit(desc, date = nil, tags = [], completed = nil)
25
+ self.desc = desc
26
+ self.date = Date.parse_or_return(date) unless date.nil?
27
+ self.tags.concat(tags.ensure_array).uniq!
28
+ self.completed = completed.nil? ? self.completed : completed
29
+ end
30
+
31
+ def toggle_completed!
32
+ @completed = !@completed
33
+ end
34
+
35
+ def toggle_archived!
36
+ @archived = !@archived
37
+ end
38
+
39
+ def completed?
40
+ @completed
41
+ end
42
+
43
+ def archived?
44
+ @archived
45
+ end
46
+
47
+ def past?
48
+ @date < Date.today
49
+ end
50
+
51
+ def today?
52
+ @date == Date.today
53
+ end
54
+
55
+ def to_s
56
+ @formatter.call
57
+ end
58
+
59
+ def to_h
60
+ {
61
+ 'id' => id,
62
+ 'desc' => desc,
63
+ 'tags' => tags,
64
+ 'completed' => completed,
65
+ 'archived' => archived,
66
+ 'uid' => uid
67
+ }
68
+ end
69
+ end
70
+ end