basecamp-sdk 0.2.1
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 +7 -0
- data/.rubocop.yml +14 -0
- data/.yardopts +6 -0
- data/README.md +293 -0
- data/Rakefile +26 -0
- data/basecamp-sdk.gemspec +46 -0
- data/lib/basecamp/auth_strategy.rb +38 -0
- data/lib/basecamp/chain_hooks.rb +45 -0
- data/lib/basecamp/client.rb +428 -0
- data/lib/basecamp/config.rb +143 -0
- data/lib/basecamp/errors.rb +289 -0
- data/lib/basecamp/generated/metadata.json +2281 -0
- data/lib/basecamp/generated/services/attachments_service.rb +24 -0
- data/lib/basecamp/generated/services/boosts_service.rb +70 -0
- data/lib/basecamp/generated/services/campfires_service.rb +122 -0
- data/lib/basecamp/generated/services/card_columns_service.rb +103 -0
- data/lib/basecamp/generated/services/card_steps_service.rb +57 -0
- data/lib/basecamp/generated/services/card_tables_service.rb +20 -0
- data/lib/basecamp/generated/services/cards_service.rb +66 -0
- data/lib/basecamp/generated/services/checkins_service.rb +157 -0
- data/lib/basecamp/generated/services/client_approvals_service.rb +28 -0
- data/lib/basecamp/generated/services/client_correspondences_service.rb +28 -0
- data/lib/basecamp/generated/services/client_replies_service.rb +30 -0
- data/lib/basecamp/generated/services/client_visibility_service.rb +21 -0
- data/lib/basecamp/generated/services/comments_service.rb +49 -0
- data/lib/basecamp/generated/services/documents_service.rb +52 -0
- data/lib/basecamp/generated/services/events_service.rb +20 -0
- data/lib/basecamp/generated/services/forwards_service.rb +67 -0
- data/lib/basecamp/generated/services/lineup_service.rb +44 -0
- data/lib/basecamp/generated/services/message_boards_service.rb +20 -0
- data/lib/basecamp/generated/services/message_types_service.rb +59 -0
- data/lib/basecamp/generated/services/messages_service.rb +75 -0
- data/lib/basecamp/generated/services/people_service.rb +73 -0
- data/lib/basecamp/generated/services/projects_service.rb +63 -0
- data/lib/basecamp/generated/services/recordings_service.rb +64 -0
- data/lib/basecamp/generated/services/reports_service.rb +56 -0
- data/lib/basecamp/generated/services/schedules_service.rb +92 -0
- data/lib/basecamp/generated/services/search_service.rb +31 -0
- data/lib/basecamp/generated/services/subscriptions_service.rb +50 -0
- data/lib/basecamp/generated/services/templates_service.rb +82 -0
- data/lib/basecamp/generated/services/timeline_service.rb +20 -0
- data/lib/basecamp/generated/services/timesheets_service.rb +81 -0
- data/lib/basecamp/generated/services/todolist_groups_service.rb +41 -0
- data/lib/basecamp/generated/services/todolists_service.rb +53 -0
- data/lib/basecamp/generated/services/todos_service.rb +106 -0
- data/lib/basecamp/generated/services/todosets_service.rb +20 -0
- data/lib/basecamp/generated/services/tools_service.rb +80 -0
- data/lib/basecamp/generated/services/uploads_service.rb +61 -0
- data/lib/basecamp/generated/services/vaults_service.rb +49 -0
- data/lib/basecamp/generated/services/webhooks_service.rb +63 -0
- data/lib/basecamp/generated/types.rb +3196 -0
- data/lib/basecamp/hooks.rb +70 -0
- data/lib/basecamp/http.rb +440 -0
- data/lib/basecamp/logger_hooks.rb +46 -0
- data/lib/basecamp/noop_hooks.rb +9 -0
- data/lib/basecamp/oauth/discovery.rb +123 -0
- data/lib/basecamp/oauth/errors.rb +35 -0
- data/lib/basecamp/oauth/exchange.rb +291 -0
- data/lib/basecamp/oauth/pkce.rb +68 -0
- data/lib/basecamp/oauth/types.rb +133 -0
- data/lib/basecamp/oauth.rb +56 -0
- data/lib/basecamp/oauth_token_provider.rb +108 -0
- data/lib/basecamp/operation_info.rb +17 -0
- data/lib/basecamp/request_info.rb +10 -0
- data/lib/basecamp/request_result.rb +14 -0
- data/lib/basecamp/security.rb +112 -0
- data/lib/basecamp/services/attachments_service.rb +33 -0
- data/lib/basecamp/services/authorization_service.rb +47 -0
- data/lib/basecamp/services/base_service.rb +146 -0
- data/lib/basecamp/services/campfires_service.rb +141 -0
- data/lib/basecamp/services/card_columns_service.rb +106 -0
- data/lib/basecamp/services/card_steps_service.rb +86 -0
- data/lib/basecamp/services/card_tables_service.rb +23 -0
- data/lib/basecamp/services/cards_service.rb +93 -0
- data/lib/basecamp/services/checkins_service.rb +127 -0
- data/lib/basecamp/services/client_approvals_service.rb +33 -0
- data/lib/basecamp/services/client_correspondences_service.rb +33 -0
- data/lib/basecamp/services/client_replies_service.rb +35 -0
- data/lib/basecamp/services/comments_service.rb +63 -0
- data/lib/basecamp/services/documents_service.rb +74 -0
- data/lib/basecamp/services/events_service.rb +27 -0
- data/lib/basecamp/services/forwards_service.rb +80 -0
- data/lib/basecamp/services/lineup_service.rb +67 -0
- data/lib/basecamp/services/message_boards_service.rb +24 -0
- data/lib/basecamp/services/message_types_service.rb +79 -0
- data/lib/basecamp/services/messages_service.rb +133 -0
- data/lib/basecamp/services/people_service.rb +73 -0
- data/lib/basecamp/services/projects_service.rb +67 -0
- data/lib/basecamp/services/recordings_service.rb +127 -0
- data/lib/basecamp/services/reports_service.rb +80 -0
- data/lib/basecamp/services/schedules_service.rb +156 -0
- data/lib/basecamp/services/search_service.rb +36 -0
- data/lib/basecamp/services/subscriptions_service.rb +67 -0
- data/lib/basecamp/services/templates_service.rb +96 -0
- data/lib/basecamp/services/timeline_service.rb +62 -0
- data/lib/basecamp/services/timesheet_service.rb +68 -0
- data/lib/basecamp/services/todolist_groups_service.rb +100 -0
- data/lib/basecamp/services/todolists_service.rb +104 -0
- data/lib/basecamp/services/todos_service.rb +156 -0
- data/lib/basecamp/services/todosets_service.rb +23 -0
- data/lib/basecamp/services/tools_service.rb +89 -0
- data/lib/basecamp/services/uploads_service.rb +84 -0
- data/lib/basecamp/services/vaults_service.rb +84 -0
- data/lib/basecamp/services/webhooks_service.rb +88 -0
- data/lib/basecamp/static_token_provider.rb +24 -0
- data/lib/basecamp/token_provider.rb +42 -0
- data/lib/basecamp/version.rb +6 -0
- data/lib/basecamp/webhooks/event.rb +52 -0
- data/lib/basecamp/webhooks/rack_middleware.rb +49 -0
- data/lib/basecamp/webhooks/receiver.rb +161 -0
- data/lib/basecamp/webhooks/verify.rb +36 -0
- data/lib/basecamp.rb +107 -0
- data/scripts/generate-metadata.rb +106 -0
- data/scripts/generate-services.rb +778 -0
- data/scripts/generate-types.rb +191 -0
- metadata +316 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for schedule (calendar) operations.
|
|
6
|
+
#
|
|
7
|
+
# Schedules are calendars within projects that contain schedule entries (events).
|
|
8
|
+
# Each project has one schedule that can optionally show todo due dates.
|
|
9
|
+
#
|
|
10
|
+
# @example Get a schedule
|
|
11
|
+
# schedule = account.schedules.get(project_id: 123, schedule_id: 456)
|
|
12
|
+
#
|
|
13
|
+
# @example List entries
|
|
14
|
+
# account.schedules.list_entries(project_id: 123, schedule_id: 456).each do |entry|
|
|
15
|
+
# puts "#{entry["summary"]} - #{entry["starts_at"]}"
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# @example Create an entry
|
|
19
|
+
# entry = account.schedules.create_entry(
|
|
20
|
+
# project_id: 123,
|
|
21
|
+
# schedule_id: 456,
|
|
22
|
+
# summary: "Team Meeting",
|
|
23
|
+
# starts_at: "2024-12-15T09:00:00Z",
|
|
24
|
+
# ends_at: "2024-12-15T10:00:00Z"
|
|
25
|
+
# )
|
|
26
|
+
class SchedulesService < BaseService
|
|
27
|
+
# Gets a specific schedule.
|
|
28
|
+
#
|
|
29
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
30
|
+
# @param schedule_id [Integer, String] schedule ID
|
|
31
|
+
# @return [Hash] schedule data
|
|
32
|
+
def get(project_id:, schedule_id:)
|
|
33
|
+
http_get(bucket_path(project_id, "/schedules/#{schedule_id}")).json
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Lists all entries on a schedule.
|
|
37
|
+
#
|
|
38
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
39
|
+
# @param schedule_id [Integer, String] schedule ID
|
|
40
|
+
# @return [Enumerator<Hash>] schedule entries
|
|
41
|
+
def list_entries(project_id:, schedule_id:)
|
|
42
|
+
paginate(bucket_path(project_id, "/schedules/#{schedule_id}/entries.json"))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Gets a specific schedule entry.
|
|
46
|
+
#
|
|
47
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
48
|
+
# @param entry_id [Integer, String] schedule entry ID
|
|
49
|
+
# @return [Hash] schedule entry data
|
|
50
|
+
def get_entry(project_id:, entry_id:)
|
|
51
|
+
http_get(bucket_path(project_id, "/schedule_entries/#{entry_id}")).json
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Creates a new entry on a schedule.
|
|
55
|
+
#
|
|
56
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
57
|
+
# @param schedule_id [Integer, String] schedule ID
|
|
58
|
+
# @param summary [String] event title
|
|
59
|
+
# @param starts_at [String] start time in RFC3339 format
|
|
60
|
+
# @param ends_at [String] end time in RFC3339 format
|
|
61
|
+
# @param description [String, nil] event description in HTML
|
|
62
|
+
# @param participant_ids [Array<Integer>, nil] person IDs to assign
|
|
63
|
+
# @param all_day [Boolean, nil] whether this is an all-day event
|
|
64
|
+
# @param notify [Boolean, nil] whether to notify participants
|
|
65
|
+
# @return [Hash] created entry
|
|
66
|
+
def create_entry(
|
|
67
|
+
project_id:,
|
|
68
|
+
schedule_id:,
|
|
69
|
+
summary:,
|
|
70
|
+
starts_at:,
|
|
71
|
+
ends_at:,
|
|
72
|
+
description: nil,
|
|
73
|
+
participant_ids: nil,
|
|
74
|
+
all_day: nil,
|
|
75
|
+
notify: nil
|
|
76
|
+
)
|
|
77
|
+
body = compact_params(
|
|
78
|
+
summary: summary,
|
|
79
|
+
starts_at: starts_at,
|
|
80
|
+
ends_at: ends_at,
|
|
81
|
+
description: description,
|
|
82
|
+
participant_ids: participant_ids,
|
|
83
|
+
all_day: all_day,
|
|
84
|
+
notify: notify
|
|
85
|
+
)
|
|
86
|
+
http_post(bucket_path(project_id, "/schedules/#{schedule_id}/entries.json"), body: body).json
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Updates an existing schedule entry.
|
|
90
|
+
#
|
|
91
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
92
|
+
# @param entry_id [Integer, String] schedule entry ID
|
|
93
|
+
# @param summary [String, nil] new title
|
|
94
|
+
# @param starts_at [String, nil] new start time in RFC3339 format
|
|
95
|
+
# @param ends_at [String, nil] new end time in RFC3339 format
|
|
96
|
+
# @param description [String, nil] new description in HTML
|
|
97
|
+
# @param participant_ids [Array<Integer>, nil] new participant IDs
|
|
98
|
+
# @param all_day [Boolean, nil] whether this is an all-day event
|
|
99
|
+
# @param notify [Boolean, nil] whether to notify participants
|
|
100
|
+
# @return [Hash] updated entry
|
|
101
|
+
def update_entry(
|
|
102
|
+
project_id:,
|
|
103
|
+
entry_id:,
|
|
104
|
+
summary: nil,
|
|
105
|
+
starts_at: nil,
|
|
106
|
+
ends_at: nil,
|
|
107
|
+
description: nil,
|
|
108
|
+
participant_ids: nil,
|
|
109
|
+
all_day: nil,
|
|
110
|
+
notify: nil
|
|
111
|
+
)
|
|
112
|
+
body = compact_params(
|
|
113
|
+
summary: summary,
|
|
114
|
+
starts_at: starts_at,
|
|
115
|
+
ends_at: ends_at,
|
|
116
|
+
description: description,
|
|
117
|
+
participant_ids: participant_ids,
|
|
118
|
+
all_day: all_day,
|
|
119
|
+
notify: notify
|
|
120
|
+
)
|
|
121
|
+
http_put(bucket_path(project_id, "/schedule_entries/#{entry_id}"), body: body).json
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Gets a specific occurrence of a recurring schedule entry.
|
|
125
|
+
#
|
|
126
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
127
|
+
# @param entry_id [Integer, String] schedule entry ID
|
|
128
|
+
# @param date [String] occurrence date in YYYY-MM-DD format
|
|
129
|
+
# @return [Hash] schedule entry occurrence
|
|
130
|
+
def get_entry_occurrence(project_id:, entry_id:, date:)
|
|
131
|
+
http_get(bucket_path(project_id, "/schedule_entries/#{entry_id}/occurrences/#{date}")).json
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Updates schedule settings.
|
|
135
|
+
#
|
|
136
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
137
|
+
# @param schedule_id [Integer, String] schedule ID
|
|
138
|
+
# @param include_due_assignments [Boolean] whether to show todo due dates
|
|
139
|
+
# @return [Hash] updated schedule
|
|
140
|
+
def update_settings(project_id:, schedule_id:, include_due_assignments:)
|
|
141
|
+
body = { include_due_assignments: include_due_assignments }
|
|
142
|
+
http_put(bucket_path(project_id, "/schedules/#{schedule_id}"), body: body).json
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Moves a schedule entry to the trash.
|
|
146
|
+
#
|
|
147
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
148
|
+
# @param entry_id [Integer, String] schedule entry ID
|
|
149
|
+
# @return [void]
|
|
150
|
+
def trash_entry(project_id:, entry_id:)
|
|
151
|
+
http_put(bucket_path(project_id, "/recordings/#{entry_id}/status/trashed.json"))
|
|
152
|
+
nil
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for search operations.
|
|
6
|
+
#
|
|
7
|
+
# Provides full-text search across all content in your Basecamp account.
|
|
8
|
+
#
|
|
9
|
+
# @example Search for content
|
|
10
|
+
# results = account.search.search(query: "quarterly report")
|
|
11
|
+
# results.each { |r| puts r["title"] }
|
|
12
|
+
#
|
|
13
|
+
# @example Get search metadata
|
|
14
|
+
# metadata = account.search.metadata
|
|
15
|
+
# puts "Available projects: #{metadata["projects"].length}"
|
|
16
|
+
class SearchService < BaseService
|
|
17
|
+
# Searches for content across the account.
|
|
18
|
+
#
|
|
19
|
+
# @param query [String] the search query string
|
|
20
|
+
# @param sort [String, nil] sort order ("created_at" or "updated_at")
|
|
21
|
+
# @return [Enumerator<Hash>] search results
|
|
22
|
+
def search(query:, sort: nil)
|
|
23
|
+
params = compact_params(query: query, sort: sort)
|
|
24
|
+
paginate("/search.json", params: params)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns metadata about available search scopes.
|
|
28
|
+
# This includes the list of projects available for filtering.
|
|
29
|
+
#
|
|
30
|
+
# @return [Hash] search metadata with available filter options
|
|
31
|
+
def metadata
|
|
32
|
+
http_get("/searches/metadata.json").json
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for subscription operations.
|
|
6
|
+
#
|
|
7
|
+
# Subscriptions control who receives notifications for a specific recording
|
|
8
|
+
# (like a todo, message, or comment). Users can subscribe or unsubscribe
|
|
9
|
+
# themselves, and you can batch update subscriptions for multiple users.
|
|
10
|
+
#
|
|
11
|
+
# @example Get subscription info
|
|
12
|
+
# subscription = account.subscriptions.get(project_id: 123, recording_id: 456)
|
|
13
|
+
# puts "Subscribed: #{subscription["subscribed"]}, Count: #{subscription["count"]}"
|
|
14
|
+
#
|
|
15
|
+
# @example Batch update subscriptions
|
|
16
|
+
# account.subscriptions.update(
|
|
17
|
+
# project_id: 123,
|
|
18
|
+
# recording_id: 456,
|
|
19
|
+
# subscriptions: [user_id_1, user_id_2],
|
|
20
|
+
# unsubscriptions: [user_id_3]
|
|
21
|
+
# )
|
|
22
|
+
class SubscriptionsService < BaseService
|
|
23
|
+
# Gets the subscription information for a recording.
|
|
24
|
+
#
|
|
25
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
26
|
+
# @param recording_id [Integer, String] recording ID
|
|
27
|
+
# @return [Hash] subscription info with subscribed, count, subscribers
|
|
28
|
+
def get(project_id:, recording_id:)
|
|
29
|
+
http_get(bucket_path(project_id, "/recordings/#{recording_id}/subscription.json")).json
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Subscribes the current user to the recording.
|
|
33
|
+
#
|
|
34
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
35
|
+
# @param recording_id [Integer, String] recording ID
|
|
36
|
+
# @return [Hash] updated subscription information
|
|
37
|
+
def subscribe(project_id:, recording_id:)
|
|
38
|
+
http_post(bucket_path(project_id, "/recordings/#{recording_id}/subscription.json")).json
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Unsubscribes the current user from the recording.
|
|
42
|
+
#
|
|
43
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
44
|
+
# @param recording_id [Integer, String] recording ID
|
|
45
|
+
# @return [void]
|
|
46
|
+
def unsubscribe(project_id:, recording_id:)
|
|
47
|
+
http_delete(bucket_path(project_id, "/recordings/#{recording_id}/subscription.json"))
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Batch modifies subscriptions by adding or removing specific users.
|
|
52
|
+
#
|
|
53
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
54
|
+
# @param recording_id [Integer, String] recording ID
|
|
55
|
+
# @param subscriptions [Array<Integer>, nil] person IDs to subscribe
|
|
56
|
+
# @param unsubscriptions [Array<Integer>, nil] person IDs to unsubscribe
|
|
57
|
+
# @return [Hash] updated subscription information
|
|
58
|
+
def update(project_id:, recording_id:, subscriptions: nil, unsubscriptions: nil)
|
|
59
|
+
body = compact_params(
|
|
60
|
+
subscriptions: subscriptions,
|
|
61
|
+
unsubscriptions: unsubscriptions
|
|
62
|
+
)
|
|
63
|
+
http_put(bucket_path(project_id, "/recordings/#{recording_id}/subscription.json"), body: body).json
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for template operations.
|
|
6
|
+
#
|
|
7
|
+
# Templates allow you to create reusable project structures.
|
|
8
|
+
#
|
|
9
|
+
# @example List templates
|
|
10
|
+
# account.templates.list.each do |template|
|
|
11
|
+
# puts template["name"]
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# @example Create a project from template
|
|
15
|
+
# construction = account.templates.create_project(
|
|
16
|
+
# template_id: 123,
|
|
17
|
+
# name: "Q1 Planning"
|
|
18
|
+
# )
|
|
19
|
+
class TemplatesService < BaseService
|
|
20
|
+
# Lists all templates visible to the current user.
|
|
21
|
+
#
|
|
22
|
+
# @return [Enumerator<Hash>] templates
|
|
23
|
+
def list
|
|
24
|
+
paginate("/templates.json")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Gets a template by ID.
|
|
28
|
+
#
|
|
29
|
+
# @param template_id [Integer, String] template ID
|
|
30
|
+
# @return [Hash] template data
|
|
31
|
+
def get(template_id:)
|
|
32
|
+
http_get("/templates/#{template_id}").json
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Creates a new template.
|
|
36
|
+
#
|
|
37
|
+
# @param name [String] template name
|
|
38
|
+
# @param description [String, nil] template description
|
|
39
|
+
# @return [Hash] created template
|
|
40
|
+
def create(name:, description: nil)
|
|
41
|
+
body = compact_params(
|
|
42
|
+
name: name,
|
|
43
|
+
description: description
|
|
44
|
+
)
|
|
45
|
+
http_post("/templates.json", body: body).json
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Updates an existing template.
|
|
49
|
+
#
|
|
50
|
+
# @param template_id [Integer, String] template ID
|
|
51
|
+
# @param name [String] new name
|
|
52
|
+
# @param description [String, nil] new description
|
|
53
|
+
# @return [Hash] updated template
|
|
54
|
+
def update(template_id:, name:, description: nil)
|
|
55
|
+
body = compact_params(
|
|
56
|
+
name: name,
|
|
57
|
+
description: description
|
|
58
|
+
)
|
|
59
|
+
http_put("/templates/#{template_id}", body: body).json
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Deletes a template.
|
|
63
|
+
#
|
|
64
|
+
# @param template_id [Integer, String] template ID
|
|
65
|
+
# @return [void]
|
|
66
|
+
def delete(template_id:)
|
|
67
|
+
http_delete("/templates/#{template_id}")
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Creates a new project from a template.
|
|
72
|
+
# This operation is asynchronous. Use get_construction to check status.
|
|
73
|
+
#
|
|
74
|
+
# @param template_id [Integer, String] template ID
|
|
75
|
+
# @param name [String] project name
|
|
76
|
+
# @param description [String, nil] project description
|
|
77
|
+
# @return [Hash] project construction status
|
|
78
|
+
def create_project(template_id:, name:, description: nil)
|
|
79
|
+
body = compact_params(
|
|
80
|
+
name: name,
|
|
81
|
+
description: description
|
|
82
|
+
)
|
|
83
|
+
http_post("/templates/#{template_id}/project_constructions.json", body: body).json
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Gets the status of a project construction.
|
|
87
|
+
#
|
|
88
|
+
# @param template_id [Integer, String] template ID
|
|
89
|
+
# @param construction_id [Integer, String] construction ID
|
|
90
|
+
# @return [Hash] project construction status
|
|
91
|
+
def get_construction(template_id:, construction_id:)
|
|
92
|
+
http_get("/templates/#{template_id}/project_constructions/#{construction_id}").json
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for timeline and progress report operations.
|
|
6
|
+
#
|
|
7
|
+
# Provides access to activity feeds showing recent activity across the account,
|
|
8
|
+
# within specific projects, or for specific people.
|
|
9
|
+
#
|
|
10
|
+
# @example Get account-wide progress report
|
|
11
|
+
# account.timeline.progress.each { |event| puts event["action"] }
|
|
12
|
+
#
|
|
13
|
+
# @example Get project timeline
|
|
14
|
+
# account.timeline.project_timeline(project_id: 123).each { |event| puts event }
|
|
15
|
+
#
|
|
16
|
+
# @example Get a person's progress (returns hash with person info)
|
|
17
|
+
# result = account.timeline.person_progress(person_id: 456)
|
|
18
|
+
# puts result["person"]["name"]
|
|
19
|
+
# result["events"].each { |event| puts event["action"] }
|
|
20
|
+
class TimelineService < BaseService
|
|
21
|
+
# Returns the account-wide progress report.
|
|
22
|
+
# This shows recent activity across all projects.
|
|
23
|
+
# Results are paginated via Link header.
|
|
24
|
+
#
|
|
25
|
+
# @return [Enumerator<Hash>] timeline events
|
|
26
|
+
def progress
|
|
27
|
+
paginate_key("/reports/progress.json", key: "events")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the activity timeline for a specific project.
|
|
31
|
+
# Results are paginated via Link header.
|
|
32
|
+
#
|
|
33
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
34
|
+
# @return [Enumerator<Hash>] timeline events
|
|
35
|
+
def project_timeline(project_id:)
|
|
36
|
+
paginate_key(bucket_path(project_id, "/timeline.json"), key: "events")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns the progress report for a specific person, including person metadata.
|
|
40
|
+
#
|
|
41
|
+
# @note Returns first page only to preserve person metadata.
|
|
42
|
+
# Use {#person_progress_events} for full paginated event stream.
|
|
43
|
+
# @param person_id [Integer, String] person ID
|
|
44
|
+
# @return [Hash] object with "person" and "events" keys
|
|
45
|
+
def person_progress(person_id:)
|
|
46
|
+
response = http_get("/reports/users/progress/#{person_id}")
|
|
47
|
+
response.json
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns all progress events for a specific person.
|
|
51
|
+
# Results are paginated via Link header.
|
|
52
|
+
#
|
|
53
|
+
# @note Does not include person metadata. Use {#person_progress} if you need
|
|
54
|
+
# the person object along with the first page of events.
|
|
55
|
+
# @param person_id [Integer, String] person ID
|
|
56
|
+
# @return [Enumerator<Hash>] timeline events
|
|
57
|
+
def person_progress_events(person_id:)
|
|
58
|
+
paginate_key("/reports/users/progress/#{person_id}", key: "events")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for timesheet report operations.
|
|
6
|
+
#
|
|
7
|
+
# Provides access to timesheet reports at account, project, and recording levels.
|
|
8
|
+
# Supports filtering by date range and person.
|
|
9
|
+
#
|
|
10
|
+
# @example Get account-wide timesheet
|
|
11
|
+
# result = account.timesheet.report
|
|
12
|
+
# result["entries"].each { |e| puts e["hours"] }
|
|
13
|
+
#
|
|
14
|
+
# @example Get project timesheet with filters
|
|
15
|
+
# result = account.timesheet.project_report(
|
|
16
|
+
# project_id: 123,
|
|
17
|
+
# from: "2024-01-01",
|
|
18
|
+
# to: "2024-01-31"
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
# @example Get timesheet for a specific todo
|
|
22
|
+
# result = account.timesheet.recording_report(
|
|
23
|
+
# project_id: 123,
|
|
24
|
+
# recording_id: 789
|
|
25
|
+
# )
|
|
26
|
+
class TimesheetService < BaseService
|
|
27
|
+
# Returns the account-wide timesheet report.
|
|
28
|
+
# This includes time entries across all projects in the account.
|
|
29
|
+
#
|
|
30
|
+
# @param from [String, nil] filter entries on or after this date (ISO 8601, e.g., "2024-01-01")
|
|
31
|
+
# @param to [String, nil] filter entries on or before this date (ISO 8601, e.g., "2024-01-31")
|
|
32
|
+
# @param person_id [Integer, nil] filter entries by a specific person
|
|
33
|
+
# @return [Hash] timesheet report object with "entries" array
|
|
34
|
+
def report(from: nil, to: nil, person_id: nil)
|
|
35
|
+
params = compact_params(from: from, to: to, person_id: person_id)
|
|
36
|
+
response = http_get("/reports/timesheet.json", params: params)
|
|
37
|
+
response.json
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns the timesheet report for a specific project.
|
|
41
|
+
#
|
|
42
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
43
|
+
# @param from [String, nil] filter entries on or after this date (ISO 8601)
|
|
44
|
+
# @param to [String, nil] filter entries on or before this date (ISO 8601)
|
|
45
|
+
# @param person_id [Integer, nil] filter entries by a specific person
|
|
46
|
+
# @return [Hash] timesheet report object with "entries" array
|
|
47
|
+
def project_report(project_id:, from: nil, to: nil, person_id: nil)
|
|
48
|
+
params = compact_params(from: from, to: to, person_id: person_id)
|
|
49
|
+
response = http_get(bucket_path(project_id, "/timesheet.json"), params: params)
|
|
50
|
+
response.json
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns the timesheet report for a specific recording within a project.
|
|
54
|
+
#
|
|
55
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
56
|
+
# @param recording_id [Integer, String] recording ID (e.g., a todo)
|
|
57
|
+
# @param from [String, nil] filter entries on or after this date (ISO 8601)
|
|
58
|
+
# @param to [String, nil] filter entries on or before this date (ISO 8601)
|
|
59
|
+
# @param person_id [Integer, nil] filter entries by a specific person
|
|
60
|
+
# @return [Hash] timesheet report object with "entries" array
|
|
61
|
+
def recording_report(project_id:, recording_id:, from: nil, to: nil, person_id: nil)
|
|
62
|
+
params = compact_params(from: from, to: to, person_id: person_id)
|
|
63
|
+
response = http_get(bucket_path(project_id, "/recordings/#{recording_id}/timesheet.json"), params: params)
|
|
64
|
+
response.json
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for todolist group operations.
|
|
6
|
+
#
|
|
7
|
+
# Todolist groups are organizational folders within a todolist that help
|
|
8
|
+
# organize related todos together.
|
|
9
|
+
#
|
|
10
|
+
# @example List groups in a todolist
|
|
11
|
+
# groups = account.todolist_groups.list(project_id: 123, todolist_id: 456)
|
|
12
|
+
#
|
|
13
|
+
# @example Create a new group
|
|
14
|
+
# group = account.todolist_groups.create(
|
|
15
|
+
# project_id: 123,
|
|
16
|
+
# todolist_id: 456,
|
|
17
|
+
# name: "Phase 1"
|
|
18
|
+
# )
|
|
19
|
+
#
|
|
20
|
+
# @example Update a group name
|
|
21
|
+
# group = account.todolist_groups.update(
|
|
22
|
+
# project_id: 123,
|
|
23
|
+
# group_id: 789,
|
|
24
|
+
# name: "Phase 1 - Complete"
|
|
25
|
+
# )
|
|
26
|
+
class TodolistGroupsService < BaseService
|
|
27
|
+
# Lists all groups in a todolist.
|
|
28
|
+
#
|
|
29
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
30
|
+
# @param todolist_id [Integer, String] todolist ID
|
|
31
|
+
# @return [Enumerator<Hash>] todolist groups
|
|
32
|
+
def list(project_id:, todolist_id:)
|
|
33
|
+
paginate(bucket_path(project_id, "/todolists/#{todolist_id}/groups.json"))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Gets a specific todolist group.
|
|
37
|
+
#
|
|
38
|
+
# Note: Groups are fetched via the todolists endpoint (polymorphic).
|
|
39
|
+
#
|
|
40
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
41
|
+
# @param group_id [Integer, String] group ID
|
|
42
|
+
# @return [Hash] group data
|
|
43
|
+
def get(project_id:, group_id:)
|
|
44
|
+
http_get(bucket_path(project_id, "/todolists/#{group_id}.json")).json
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Creates a new group in a todolist.
|
|
48
|
+
#
|
|
49
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
50
|
+
# @param todolist_id [Integer, String] todolist ID
|
|
51
|
+
# @param name [String] group name (required)
|
|
52
|
+
# @return [Hash] created group
|
|
53
|
+
# @raise [ArgumentError] if name is empty
|
|
54
|
+
def create(project_id:, todolist_id:, name:)
|
|
55
|
+
raise ArgumentError, "group name is required" if name.nil? || name.empty?
|
|
56
|
+
|
|
57
|
+
body = { name: name }
|
|
58
|
+
http_post(bucket_path(project_id, "/todolists/#{todolist_id}/groups.json"), body: body).json
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Updates an existing todolist group.
|
|
62
|
+
#
|
|
63
|
+
# Note: Groups are updated via the todolists endpoint (polymorphic).
|
|
64
|
+
#
|
|
65
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
66
|
+
# @param group_id [Integer, String] group ID
|
|
67
|
+
# @param name [String, nil] new group name
|
|
68
|
+
# @return [Hash] updated group
|
|
69
|
+
def update(project_id:, group_id:, name: nil)
|
|
70
|
+
body = compact_params(name: name)
|
|
71
|
+
http_put(bucket_path(project_id, "/todolists/#{group_id}.json"), body: body).json
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Repositions a group within its todolist.
|
|
75
|
+
#
|
|
76
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
77
|
+
# @param group_id [Integer, String] group ID
|
|
78
|
+
# @param position [Integer] new position (1-based, 1 = first position)
|
|
79
|
+
# @return [void]
|
|
80
|
+
# @raise [ArgumentError] if position is less than 1
|
|
81
|
+
def reposition(project_id:, group_id:, position:)
|
|
82
|
+
raise ArgumentError, "position must be at least 1" if position < 1
|
|
83
|
+
|
|
84
|
+
http_put(bucket_path(project_id, "/todolists/#{group_id}/position.json"), body: { position: position })
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Moves a group to the trash.
|
|
89
|
+
# Trashed groups can be recovered from the trash.
|
|
90
|
+
#
|
|
91
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
92
|
+
# @param group_id [Integer, String] group ID
|
|
93
|
+
# @return [void]
|
|
94
|
+
def trash(project_id:, group_id:)
|
|
95
|
+
http_put(bucket_path(project_id, "/recordings/#{group_id}/status/trashed.json"))
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for todolist operations.
|
|
6
|
+
#
|
|
7
|
+
# Todolists are collections of todos within a todoset. A project has one
|
|
8
|
+
# todoset which contains multiple todolists.
|
|
9
|
+
#
|
|
10
|
+
# @example List todolists
|
|
11
|
+
# account.todolists.list(project_id: 123, todoset_id: 456).each do |list|
|
|
12
|
+
# puts "#{list["name"]} - #{list["completed_ratio"]}"
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# @example Create a todolist
|
|
16
|
+
# todolist = account.todolists.create(
|
|
17
|
+
# project_id: 123,
|
|
18
|
+
# todoset_id: 456,
|
|
19
|
+
# name: "Launch Tasks"
|
|
20
|
+
# )
|
|
21
|
+
class TodolistsService < BaseService
|
|
22
|
+
# Lists all todolists in a todoset.
|
|
23
|
+
#
|
|
24
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
25
|
+
# @param todoset_id [Integer, String] todoset ID
|
|
26
|
+
# @param status [String, nil] filter by status ("archived", "trashed")
|
|
27
|
+
# @return [Enumerator<Hash>] todolists
|
|
28
|
+
def list(project_id:, todoset_id:, status: nil)
|
|
29
|
+
params = compact_params(status: status)
|
|
30
|
+
paginate(bucket_path(project_id, "/todosets/#{todoset_id}/todolists.json"), params: params)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Gets a specific todolist.
|
|
34
|
+
#
|
|
35
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
36
|
+
# @param todolist_id [Integer, String] todolist ID
|
|
37
|
+
# @return [Hash] todolist data
|
|
38
|
+
def get(project_id:, todolist_id:)
|
|
39
|
+
http_get(bucket_path(project_id, "/todolists/#{todolist_id}")).json
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Creates a new todolist.
|
|
43
|
+
#
|
|
44
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
45
|
+
# @param todoset_id [Integer, String] todoset ID
|
|
46
|
+
# @param name [String] todolist name
|
|
47
|
+
# @param description [String, nil] todolist description in HTML
|
|
48
|
+
# @return [Hash] created todolist
|
|
49
|
+
def create(project_id:, todoset_id:, name:, description: nil)
|
|
50
|
+
body = compact_params(
|
|
51
|
+
name: name,
|
|
52
|
+
description: description
|
|
53
|
+
)
|
|
54
|
+
http_post(bucket_path(project_id, "/todosets/#{todoset_id}/todolists.json"), body: body).json
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Updates a todolist.
|
|
58
|
+
#
|
|
59
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
60
|
+
# @param todolist_id [Integer, String] todolist ID
|
|
61
|
+
# @param name [String, nil] new name
|
|
62
|
+
# @param description [String, nil] new description
|
|
63
|
+
# @return [Hash] updated todolist
|
|
64
|
+
def update(project_id:, todolist_id:, name: nil, description: nil)
|
|
65
|
+
body = compact_params(
|
|
66
|
+
name: name,
|
|
67
|
+
description: description
|
|
68
|
+
)
|
|
69
|
+
http_put(bucket_path(project_id, "/todolists/#{todolist_id}"), body: body).json
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Lists all todolist groups within a todolist.
|
|
73
|
+
#
|
|
74
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
75
|
+
# @param todolist_id [Integer, String] todolist ID
|
|
76
|
+
# @return [Enumerator<Hash>] todolist groups
|
|
77
|
+
def list_groups(project_id:, todolist_id:)
|
|
78
|
+
paginate(bucket_path(project_id, "/todolists/#{todolist_id}/groups.json"))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Creates a new todolist group.
|
|
82
|
+
#
|
|
83
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
84
|
+
# @param todolist_id [Integer, String] todolist ID
|
|
85
|
+
# @param name [String] group name
|
|
86
|
+
# @return [Hash] created group
|
|
87
|
+
def create_group(project_id:, todolist_id:, name:)
|
|
88
|
+
body = { name: name }
|
|
89
|
+
http_post(bucket_path(project_id, "/todolists/#{todolist_id}/groups.json"), body: body).json
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Repositions a todolist group.
|
|
93
|
+
#
|
|
94
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
95
|
+
# @param todolist_id [Integer, String] todolist ID (the group's ID)
|
|
96
|
+
# @param position [Integer] new position (1-based)
|
|
97
|
+
# @return [void]
|
|
98
|
+
def reposition_group(project_id:, todolist_id:, position:)
|
|
99
|
+
http_put(bucket_path(project_id, "/todolists/#{todolist_id}/position.json"), body: { position: position })
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|