nin 0.0.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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