moco-ruby 0.1.2 → 1.0.0.alpha
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -3
- data/CHANGELOG.md +65 -4
- data/Gemfile +2 -1
- data/Gemfile.lock +70 -23
- data/README.md +199 -55
- data/examples/v2_api_example.rb +73 -0
- data/lib/moco/client.rb +47 -0
- data/lib/moco/collection_proxy.rb +190 -0
- data/lib/moco/connection.rb +62 -0
- data/lib/moco/entities/activity.rb +96 -0
- data/lib/moco/entities/base_entity.rb +303 -0
- data/lib/moco/entities/company.rb +28 -0
- data/lib/moco/entities/deal.rb +24 -0
- data/lib/moco/entities/expense.rb +29 -0
- data/lib/moco/entities/holiday.rb +25 -0
- data/lib/moco/entities/invoice.rb +53 -0
- data/lib/moco/entities/planning_entry.rb +26 -0
- data/lib/moco/entities/presence.rb +30 -0
- data/lib/moco/entities/project.rb +39 -0
- data/lib/moco/entities/schedule.rb +26 -0
- data/lib/moco/entities/task.rb +20 -0
- data/lib/moco/entities/user.rb +33 -0
- data/lib/moco/entities/web_hook.rb +27 -0
- data/lib/moco/entities.rb +11 -4
- data/lib/moco/entity_collection.rb +59 -0
- data/lib/moco/helpers.rb +1 -0
- data/lib/moco/nested_collection_proxy.rb +40 -0
- data/lib/moco/sync.rb +70 -29
- data/lib/moco/version.rb +1 -1
- data/lib/moco.rb +26 -2
- data/mocurl.rb +51 -34
- data/sync_activity.rb +4 -4
- metadata +43 -10
- data/lib/moco/api.rb +0 -194
data/lib/moco/version.rb
CHANGED
data/lib/moco.rb
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support"
|
|
4
|
+
require "active_support/core_ext"
|
|
5
|
+
require "active_support/inflector"
|
|
6
|
+
|
|
3
7
|
require_relative "moco/version"
|
|
4
|
-
|
|
5
|
-
|
|
8
|
+
|
|
9
|
+
# New API (v2)
|
|
10
|
+
require_relative "moco/entities/base_entity"
|
|
11
|
+
require_relative "moco/entities/project"
|
|
12
|
+
require_relative "moco/entities/activity"
|
|
13
|
+
require_relative "moco/entities/user"
|
|
14
|
+
require_relative "moco/entities/company"
|
|
15
|
+
require_relative "moco/entities/task"
|
|
16
|
+
require_relative "moco/entities/invoice"
|
|
17
|
+
require_relative "moco/entities/deal"
|
|
18
|
+
require_relative "moco/entities/expense"
|
|
19
|
+
require_relative "moco/entities/web_hook"
|
|
20
|
+
require_relative "moco/entities/schedule"
|
|
21
|
+
require_relative "moco/entities/presence"
|
|
22
|
+
require_relative "moco/entities/holiday"
|
|
23
|
+
require_relative "moco/entities/planning_entry"
|
|
24
|
+
require_relative "moco/client"
|
|
25
|
+
require_relative "moco/connection"
|
|
26
|
+
require_relative "moco/collection_proxy"
|
|
27
|
+
require_relative "moco/nested_collection_proxy"
|
|
28
|
+
require_relative "moco/entity_collection"
|
|
29
|
+
|
|
6
30
|
require_relative "moco/sync"
|
|
7
31
|
|
|
8
32
|
module MOCO
|
data/mocurl.rb
CHANGED
|
@@ -58,42 +58,59 @@ end
|
|
|
58
58
|
|
|
59
59
|
# Load default API key from config
|
|
60
60
|
config = YAML.load_file("config.yml")
|
|
61
|
-
options[:api_key] ||= config["instances"].
|
|
61
|
+
options[:api_key] ||= config["instances"].dig(subdomain, "api_key")
|
|
62
62
|
|
|
63
63
|
warn "Error: No API key found for `#{subdomain}' and none given, continuing without" if options[:api_key].nil?
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
65
|
+
client = MOCO::Client.new(subdomain: subdomain, api_key: options[:api_key])
|
|
66
|
+
|
|
67
|
+
# Extract path from URL
|
|
68
|
+
path = url.gsub(%r{https?://#{subdomain}\.mocoapp\.com/api/v1/}, "")
|
|
69
|
+
|
|
70
|
+
begin
|
|
71
|
+
# Make request using the client's connection directly
|
|
72
|
+
result = case options[:method]
|
|
73
|
+
when "GET"
|
|
74
|
+
client.connection.get(path)
|
|
75
|
+
when "DELETE"
|
|
76
|
+
client.connection.delete(path)
|
|
77
|
+
when "POST"
|
|
78
|
+
client.connection.post(path, options[:data])
|
|
79
|
+
when "PUT"
|
|
80
|
+
client.connection.put(path, options[:data])
|
|
81
|
+
when "PATCH"
|
|
82
|
+
client.connection.patch(path, options[:data])
|
|
83
|
+
else
|
|
84
|
+
puts "Error: Invalid HTTP Method: #{options[:method]}"
|
|
85
|
+
exit 1
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if options[:verbose]
|
|
89
|
+
puts "> #{options[:method]} #{url}"
|
|
90
|
+
# Print request details if available
|
|
91
|
+
if result.env&.request_headers
|
|
92
|
+
puts(result.env.request_headers.map do |k, v|
|
|
93
|
+
"> #{k}: #{k == "Authorization" ? "#{v[0...16]}<REDACTED>#{v[-4..]}" : v}"
|
|
94
|
+
end)
|
|
95
|
+
puts ">"
|
|
96
|
+
puts result.env.request_body.split.map { |l| "> #{l}" }.join if result.env.request_body
|
|
97
|
+
puts "---"
|
|
98
|
+
puts "< #{result.status} #{result.reason_phrase}"
|
|
99
|
+
puts(result.headers.map { |k, v| "< #{k}: #{v}" })
|
|
100
|
+
else
|
|
101
|
+
puts "> Request details not available in this response format"
|
|
102
|
+
end
|
|
103
|
+
puts ""
|
|
104
|
+
end
|
|
82
105
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
puts "
|
|
92
|
-
|
|
93
|
-
puts ""
|
|
94
|
-
end
|
|
95
|
-
if options[:no_format]
|
|
96
|
-
puts result.body.to_json
|
|
97
|
-
else
|
|
98
|
-
puts JSON.pretty_generate(result.body)
|
|
106
|
+
# Format the response
|
|
107
|
+
response_data = result.body
|
|
108
|
+
if options[:no_format]
|
|
109
|
+
puts response_data.to_json
|
|
110
|
+
else
|
|
111
|
+
puts JSON.pretty_generate(response_data)
|
|
112
|
+
end
|
|
113
|
+
rescue StandardError => e
|
|
114
|
+
puts "Error: #{e.message}"
|
|
115
|
+
exit 1
|
|
99
116
|
end
|
data/sync_activity.rb
CHANGED
|
@@ -64,12 +64,12 @@ config = YAML.load_file("config.yml")
|
|
|
64
64
|
source_config = config["instances"].fetch(source_instance, nil)
|
|
65
65
|
target_config = config["instances"].fetch(target_instance, nil)
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
source_client = MOCO::Client.new(subdomain: source_instance, api_key: source_config["api_key"])
|
|
68
|
+
target_client = MOCO::Client.new(subdomain: target_instance, api_key: target_config["api_key"])
|
|
69
69
|
|
|
70
70
|
syncer = MOCO::Sync.new(
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
source_client,
|
|
72
|
+
target_client,
|
|
73
73
|
project_match_threshold: options[:match_project_threshold],
|
|
74
74
|
task_match_threshold: options[:match_task_threshold],
|
|
75
75
|
filters: {
|
metadata
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: moco-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0.alpha
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Teal Bauer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-04-
|
|
11
|
+
date: 2025-04-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '7.0'
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: faraday
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -55,21 +69,40 @@ files:
|
|
|
55
69
|
- README.md
|
|
56
70
|
- Rakefile
|
|
57
71
|
- config.yml.sample
|
|
72
|
+
- examples/v2_api_example.rb
|
|
58
73
|
- lib/moco.rb
|
|
59
|
-
- lib/moco/
|
|
74
|
+
- lib/moco/client.rb
|
|
75
|
+
- lib/moco/collection_proxy.rb
|
|
76
|
+
- lib/moco/connection.rb
|
|
60
77
|
- lib/moco/entities.rb
|
|
78
|
+
- lib/moco/entities/activity.rb
|
|
79
|
+
- lib/moco/entities/base_entity.rb
|
|
80
|
+
- lib/moco/entities/company.rb
|
|
81
|
+
- lib/moco/entities/deal.rb
|
|
82
|
+
- lib/moco/entities/expense.rb
|
|
83
|
+
- lib/moco/entities/holiday.rb
|
|
84
|
+
- lib/moco/entities/invoice.rb
|
|
85
|
+
- lib/moco/entities/planning_entry.rb
|
|
86
|
+
- lib/moco/entities/presence.rb
|
|
87
|
+
- lib/moco/entities/project.rb
|
|
88
|
+
- lib/moco/entities/schedule.rb
|
|
89
|
+
- lib/moco/entities/task.rb
|
|
90
|
+
- lib/moco/entities/user.rb
|
|
91
|
+
- lib/moco/entities/web_hook.rb
|
|
92
|
+
- lib/moco/entity_collection.rb
|
|
61
93
|
- lib/moco/helpers.rb
|
|
94
|
+
- lib/moco/nested_collection_proxy.rb
|
|
62
95
|
- lib/moco/sync.rb
|
|
63
96
|
- lib/moco/version.rb
|
|
64
97
|
- mocurl.rb
|
|
65
98
|
- sync_activity.rb
|
|
66
|
-
homepage: https://github.com/
|
|
99
|
+
homepage: https://github.com/starsong-consulting/moco-ruby
|
|
67
100
|
licenses:
|
|
68
101
|
- Apache-2.0
|
|
69
102
|
metadata:
|
|
70
|
-
homepage_uri: https://github.com/
|
|
71
|
-
source_code_uri: https://github.com/
|
|
72
|
-
changelog_uri: https://github.com/
|
|
103
|
+
homepage_uri: https://github.com/starsong-consulting/moco-ruby
|
|
104
|
+
source_code_uri: https://github.com/starsong-consulting/moco-ruby
|
|
105
|
+
changelog_uri: https://github.com/starsong-consulting/moco-ruby/blob/main/CHANGELOG.md
|
|
73
106
|
rubygems_mfa_required: 'true'
|
|
74
107
|
post_install_message:
|
|
75
108
|
rdoc_options: []
|
|
@@ -79,12 +112,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
79
112
|
requirements:
|
|
80
113
|
- - ">="
|
|
81
114
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: 2.
|
|
115
|
+
version: 3.2.0
|
|
83
116
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
117
|
requirements:
|
|
85
|
-
- - "
|
|
118
|
+
- - ">"
|
|
86
119
|
- !ruby/object:Gem::Version
|
|
87
|
-
version:
|
|
120
|
+
version: 1.3.1
|
|
88
121
|
requirements: []
|
|
89
122
|
rubygems_version: 3.4.1
|
|
90
123
|
signing_key:
|
data/lib/moco/api.rb
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "faraday"
|
|
4
|
-
require_relative "entities"
|
|
5
|
-
|
|
6
|
-
module MOCO
|
|
7
|
-
# MOCO::API abstracts access to the MOCO API and its entities
|
|
8
|
-
class API
|
|
9
|
-
def initialize(subdomain, api_key)
|
|
10
|
-
@subdomain = subdomain
|
|
11
|
-
@api_key = api_key
|
|
12
|
-
@conn = Faraday.new do |f|
|
|
13
|
-
f.request :json
|
|
14
|
-
f.response :json
|
|
15
|
-
f.request :authorization, "Token", "token=#{@api_key}" if @api_key
|
|
16
|
-
f.url_prefix = "https://#{@subdomain}.mocoapp.com/api/v1"
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
%w[get post put patch delete].each do |method|
|
|
21
|
-
define_method(method) do |path, *args|
|
|
22
|
-
@conn.send(method, path, *args)
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def get_projects(**args)
|
|
27
|
-
response = @conn.get("projects?#{Faraday::Utils.build_query(args)}")
|
|
28
|
-
parse_projects_response(response.body)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def get_assigned_projects(**args)
|
|
32
|
-
response = @conn.get("projects/assigned?#{Faraday::Utils.build_query(args)}")
|
|
33
|
-
parse_projects_response(response.body)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def get_activities(filters = {})
|
|
37
|
-
response = @conn.get("activities?#{Faraday::Utils.build_query(filters)}")
|
|
38
|
-
parse_activities_response(response.body)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def get_activity(id)
|
|
42
|
-
response = @conn.get("activities/#{id}")
|
|
43
|
-
parse_activities_response([response.body]).first
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def create_activities_bulk(activities)
|
|
47
|
-
api_entities = activities.map do |activity|
|
|
48
|
-
activity.to_h.except(:id, :project, :user, :customer).tap do |h|
|
|
49
|
-
h[:project_id] = activity.project.id
|
|
50
|
-
h[:task_id] = activity.task.id
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
@conn.post("activities/bulk", { activities: api_entities })
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def create_activity(activity)
|
|
57
|
-
api_entity = activity.to_h.except(:id, :project, :user, :customer).tap do |h|
|
|
58
|
-
h[:project_id] = activity.project.id
|
|
59
|
-
h[:task_id] = activity.task.id
|
|
60
|
-
end
|
|
61
|
-
@conn.post("activities", api_entity)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def update_activity(activity)
|
|
65
|
-
api_entity = activity.to_h.except(:project, :user, :customer).tap do |h|
|
|
66
|
-
h[:project_id] = activity.project.id
|
|
67
|
-
h[:task_id] = activity.task.id
|
|
68
|
-
end
|
|
69
|
-
@conn.put("activities/#{activity.id}", api_entity)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def start_activity_timer(activity_id)
|
|
73
|
-
@conn.patch("activities/#{activity_id}/start_timer")
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def stop_activity_timer(activity_id)
|
|
77
|
-
@conn.patch("activities/#{activity_id}/stop_timer")
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def disregard_activities(reason:, activity_ids:, company_id:, project_id: nil)
|
|
81
|
-
payload = {
|
|
82
|
-
reason: reason,
|
|
83
|
-
activity_ids: activity_ids,
|
|
84
|
-
company_id: company_id
|
|
85
|
-
}
|
|
86
|
-
payload[:project_id] = project_id if project_id
|
|
87
|
-
@conn.post("activities/disregard", payload)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def delete_activity(activity_id)
|
|
91
|
-
@conn.delete("activities/#{activity_id}")
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def archive_project(project_id)
|
|
95
|
-
@conn.put("projects/#{project_id}/archive")
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def unarchive_project(project_id)
|
|
99
|
-
@conn.put("projects/#{project_id}/unarchive")
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def get_project_report(project_id)
|
|
103
|
-
@conn.get("projects/#{project_id}/report")
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def share_project_report(project_id)
|
|
107
|
-
@conn.put("projects/#{project_id}/share")
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def disable_project_report_sharing(project_id)
|
|
111
|
-
@conn.put("projects/#{project_id}/disable_share")
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def assign_project_to_group(project_id, project_group_id)
|
|
115
|
-
@conn.put("projects/#{project_id}/assign_project_group", { project_group_id: project_group_id })
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def unassign_project_from_group(project_id)
|
|
119
|
-
@conn.put("projects/#{project_id}/unassign_project_group")
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
private
|
|
123
|
-
|
|
124
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
125
|
-
def parse_projects_response(data)
|
|
126
|
-
data.map do |project_data|
|
|
127
|
-
Project.new.tap do |project|
|
|
128
|
-
project.id = project_data["id"]
|
|
129
|
-
project.name = project_data["name"]
|
|
130
|
-
project.customer = parse_customer_reference(project_data["customer"])
|
|
131
|
-
project.tasks = project_data["tasks"].map do |task_data|
|
|
132
|
-
Task.new.tap do |task|
|
|
133
|
-
task.id = task_data["id"]
|
|
134
|
-
task.name = task_data["name"]
|
|
135
|
-
task.project_id = task_data["project_id"]
|
|
136
|
-
task.billable = task_data["billable"]
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
143
|
-
|
|
144
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
145
|
-
def parse_activities_response(data)
|
|
146
|
-
data.map do |activity_data|
|
|
147
|
-
Activity.new.tap do |activity|
|
|
148
|
-
activity.id = activity_data["id"]
|
|
149
|
-
activity.date = activity_data["date"]
|
|
150
|
-
activity.description = activity_data["description"]
|
|
151
|
-
activity.user = parse_user_reference(activity_data["user"])
|
|
152
|
-
activity.customer = parse_customer_reference(activity_data["customer"])
|
|
153
|
-
activity.project = parse_project_reference(activity_data["project"])
|
|
154
|
-
activity.task = parse_task_reference(activity_data["task"])
|
|
155
|
-
activity.hours = activity_data["hours"]
|
|
156
|
-
activity.seconds = activity_data["seconds"]
|
|
157
|
-
activity.billable = activity_data["billable"]
|
|
158
|
-
activity.billed = activity_data["billed"]
|
|
159
|
-
activity.tag = activity_data["tag"]
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
164
|
-
|
|
165
|
-
def parse_project_reference(project_data)
|
|
166
|
-
Project.new.tap do |project|
|
|
167
|
-
project.id = project_data["id"]
|
|
168
|
-
project.name = project_data["name"]
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def parse_task_reference(task_data)
|
|
173
|
-
Task.new.tap do |task|
|
|
174
|
-
task.id = task_data["id"]
|
|
175
|
-
task.name = task_data["name"]
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def parse_user_reference(user_data)
|
|
180
|
-
User.new.tap do |user|
|
|
181
|
-
user.id = user_data["id"]
|
|
182
|
-
user.firstname = user_data["firstname"]
|
|
183
|
-
user.lastname = user_data["lastname"]
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
def parse_customer_reference(customer_data)
|
|
188
|
-
Customer.new.tap do |customer|
|
|
189
|
-
customer.id = customer_data["id"]
|
|
190
|
-
customer.name = customer_data["name"]
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
end
|