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 todo operations.
|
|
6
|
+
#
|
|
7
|
+
# @example List todos in a todolist
|
|
8
|
+
# account.todos.list(project_id: 123, todolist_id: 456).each do |todo|
|
|
9
|
+
# puts "#{todo["content"]} (#{todo["completed"] ? "done" : "pending"})"
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# @example Create a todo
|
|
13
|
+
# todo = account.todos.create(
|
|
14
|
+
# project_id: 123,
|
|
15
|
+
# todolist_id: 456,
|
|
16
|
+
# content: "Write documentation",
|
|
17
|
+
# assignee_ids: [789]
|
|
18
|
+
# )
|
|
19
|
+
#
|
|
20
|
+
# @example Complete a todo
|
|
21
|
+
# account.todos.complete(project_id: 123, todo_id: 456)
|
|
22
|
+
class TodosService < BaseService
|
|
23
|
+
# Lists todos in a todolist.
|
|
24
|
+
#
|
|
25
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
26
|
+
# @param todolist_id [Integer, String] todolist ID
|
|
27
|
+
# @param status [String, nil] filter by status ("archived", "trashed")
|
|
28
|
+
# @param completed [Boolean, nil] filter by completion status
|
|
29
|
+
# @return [Enumerator<Hash>] todos
|
|
30
|
+
def list(project_id:, todolist_id:, status: nil, completed: nil)
|
|
31
|
+
params = compact_params(status: status, completed: completed)
|
|
32
|
+
paginate(bucket_path(project_id, "/todolists/#{todolist_id}/todos.json"), params: params)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Gets a specific todo.
|
|
36
|
+
#
|
|
37
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
38
|
+
# @param todo_id [Integer, String] todo ID
|
|
39
|
+
# @return [Hash] todo data
|
|
40
|
+
def get(project_id:, todo_id:)
|
|
41
|
+
http_get(bucket_path(project_id, "/todos/#{todo_id}.json")).json
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Creates a new todo.
|
|
45
|
+
#
|
|
46
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
47
|
+
# @param todolist_id [Integer, String] todolist ID
|
|
48
|
+
# @param content [String] todo content (can include HTML)
|
|
49
|
+
# @param description [String, nil] extended description (HTML)
|
|
50
|
+
# @param assignee_ids [Array<Integer>, nil] user IDs to assign
|
|
51
|
+
# @param completion_subscriber_ids [Array<Integer>, nil] user IDs to notify on completion
|
|
52
|
+
# @param notify [Boolean] whether to notify assignees
|
|
53
|
+
# @param due_on [String, nil] due date (YYYY-MM-DD)
|
|
54
|
+
# @param starts_on [String, nil] start date (YYYY-MM-DD)
|
|
55
|
+
# @return [Hash] created todo
|
|
56
|
+
def create(
|
|
57
|
+
project_id:,
|
|
58
|
+
todolist_id:,
|
|
59
|
+
content:,
|
|
60
|
+
description: nil,
|
|
61
|
+
assignee_ids: nil,
|
|
62
|
+
completion_subscriber_ids: nil,
|
|
63
|
+
notify: true,
|
|
64
|
+
due_on: nil,
|
|
65
|
+
starts_on: nil
|
|
66
|
+
)
|
|
67
|
+
body = compact_params(
|
|
68
|
+
content: content,
|
|
69
|
+
description: description,
|
|
70
|
+
assignee_ids: assignee_ids,
|
|
71
|
+
completion_subscriber_ids: completion_subscriber_ids,
|
|
72
|
+
notify: notify,
|
|
73
|
+
due_on: due_on,
|
|
74
|
+
starts_on: starts_on
|
|
75
|
+
)
|
|
76
|
+
http_post(bucket_path(project_id, "/todolists/#{todolist_id}/todos.json"), body: body).json
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Updates a todo.
|
|
80
|
+
#
|
|
81
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
82
|
+
# @param todo_id [Integer, String] todo ID
|
|
83
|
+
# @param content [String, nil] new content
|
|
84
|
+
# @param description [String, nil] new description
|
|
85
|
+
# @param assignee_ids [Array<Integer>, nil] new assignee IDs
|
|
86
|
+
# @param completion_subscriber_ids [Array<Integer>, nil] new completion subscriber IDs
|
|
87
|
+
# @param notify [Boolean, nil] whether to notify
|
|
88
|
+
# @param due_on [String, nil] new due date
|
|
89
|
+
# @param starts_on [String, nil] new start date
|
|
90
|
+
# @return [Hash] updated todo
|
|
91
|
+
def update(
|
|
92
|
+
project_id:,
|
|
93
|
+
todo_id:,
|
|
94
|
+
content: nil,
|
|
95
|
+
description: nil,
|
|
96
|
+
assignee_ids: nil,
|
|
97
|
+
completion_subscriber_ids: nil,
|
|
98
|
+
notify: nil,
|
|
99
|
+
due_on: nil,
|
|
100
|
+
starts_on: nil
|
|
101
|
+
)
|
|
102
|
+
body = compact_params(
|
|
103
|
+
content: content,
|
|
104
|
+
description: description,
|
|
105
|
+
assignee_ids: assignee_ids,
|
|
106
|
+
completion_subscriber_ids: completion_subscriber_ids,
|
|
107
|
+
notify: notify,
|
|
108
|
+
due_on: due_on,
|
|
109
|
+
starts_on: starts_on
|
|
110
|
+
)
|
|
111
|
+
http_put(bucket_path(project_id, "/todos/#{todo_id}.json"), body: body).json
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Completes a todo.
|
|
115
|
+
#
|
|
116
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
117
|
+
# @param todo_id [Integer, String] todo ID
|
|
118
|
+
# @return [void]
|
|
119
|
+
def complete(project_id:, todo_id:)
|
|
120
|
+
http_post(bucket_path(project_id, "/todos/#{todo_id}/completion.json"))
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Uncompletes a todo.
|
|
125
|
+
#
|
|
126
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
127
|
+
# @param todo_id [Integer, String] todo ID
|
|
128
|
+
# @return [void]
|
|
129
|
+
def uncomplete(project_id:, todo_id:)
|
|
130
|
+
http_delete(bucket_path(project_id, "/todos/#{todo_id}/completion.json"))
|
|
131
|
+
nil
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Repositions a todo within its todolist.
|
|
135
|
+
#
|
|
136
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
137
|
+
# @param todo_id [Integer, String] todo ID
|
|
138
|
+
# @param position [Integer] new position (1-based)
|
|
139
|
+
# @return [void]
|
|
140
|
+
def reposition(project_id:, todo_id:, position:)
|
|
141
|
+
http_put(bucket_path(project_id, "/todos/#{todo_id}/position.json"), body: { position: position })
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Trashes a todo.
|
|
146
|
+
#
|
|
147
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
148
|
+
# @param todo_id [Integer, String] todo ID
|
|
149
|
+
# @return [void]
|
|
150
|
+
def trash(project_id:, todo_id:)
|
|
151
|
+
http_delete(bucket_path(project_id, "/todos/#{todo_id}.json"))
|
|
152
|
+
nil
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for todoset operations.
|
|
6
|
+
#
|
|
7
|
+
# Each project has one todoset which is the container for all todolists.
|
|
8
|
+
#
|
|
9
|
+
# @example Get a todoset
|
|
10
|
+
# todoset = account.todosets.get(project_id: 123, todoset_id: 456)
|
|
11
|
+
# puts "#{todoset["name"]} - #{todoset["todolists_count"]} lists"
|
|
12
|
+
class TodosetsService < BaseService
|
|
13
|
+
# Gets a specific todoset.
|
|
14
|
+
#
|
|
15
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
16
|
+
# @param todoset_id [Integer, String] todoset ID
|
|
17
|
+
# @return [Hash] todoset data
|
|
18
|
+
def get(project_id:, todoset_id:)
|
|
19
|
+
http_get(bucket_path(project_id, "/todosets/#{todoset_id}")).json
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for dock tool operations.
|
|
6
|
+
#
|
|
7
|
+
# Tools are dock items in a Basecamp project (e.g., Message Board,
|
|
8
|
+
# Todos, Schedule, etc.). This service allows you to manage these tools.
|
|
9
|
+
#
|
|
10
|
+
# @example Get a tool
|
|
11
|
+
# tool = account.tools.get(project_id: 123, tool_id: 456)
|
|
12
|
+
# puts "#{tool["name"]} - #{tool["enabled"] ? "enabled" : "disabled"}"
|
|
13
|
+
#
|
|
14
|
+
# @example Enable and reposition a tool
|
|
15
|
+
# account.tools.enable(project_id: 123, tool_id: 456)
|
|
16
|
+
# account.tools.reposition(project_id: 123, tool_id: 456, position: 1)
|
|
17
|
+
class ToolsService < BaseService
|
|
18
|
+
# Gets a tool by ID.
|
|
19
|
+
#
|
|
20
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
21
|
+
# @param tool_id [Integer, String] tool ID
|
|
22
|
+
# @return [Hash] tool data
|
|
23
|
+
def get(project_id:, tool_id:)
|
|
24
|
+
http_get(bucket_path(project_id, "/dock/tools/#{tool_id}")).json
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Clones an existing tool to create a new one.
|
|
28
|
+
#
|
|
29
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
30
|
+
# @param source_tool_id [Integer, String] ID of the tool to clone
|
|
31
|
+
# @return [Hash] newly created tool
|
|
32
|
+
def clone(project_id:, source_tool_id:)
|
|
33
|
+
http_post(bucket_path(project_id, "/dock/tools/#{source_tool_id}/clone.json")).json
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Updates (renames) an existing tool.
|
|
37
|
+
#
|
|
38
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
39
|
+
# @param tool_id [Integer, String] tool ID
|
|
40
|
+
# @param title [String] new title for the tool
|
|
41
|
+
# @return [Hash] updated tool
|
|
42
|
+
def update(project_id:, tool_id:, title:)
|
|
43
|
+
http_put(bucket_path(project_id, "/dock/tools/#{tool_id}"), body: { title: title }).json
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Deletes a tool (moves it to trash).
|
|
47
|
+
#
|
|
48
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
49
|
+
# @param tool_id [Integer, String] tool ID
|
|
50
|
+
# @return [void]
|
|
51
|
+
def delete(project_id:, tool_id:)
|
|
52
|
+
http_delete(bucket_path(project_id, "/dock/tools/#{tool_id}"))
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Enables a tool (shows it on the project dock).
|
|
57
|
+
#
|
|
58
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
59
|
+
# @param tool_id [Integer, String] tool ID
|
|
60
|
+
# @return [void]
|
|
61
|
+
def enable(project_id:, tool_id:)
|
|
62
|
+
http_post(bucket_path(project_id, "/dock/tools/#{tool_id}/position.json"))
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Disables a tool (hides it from the project dock).
|
|
67
|
+
#
|
|
68
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
69
|
+
# @param tool_id [Integer, String] tool ID
|
|
70
|
+
# @return [void]
|
|
71
|
+
def disable(project_id:, tool_id:)
|
|
72
|
+
http_delete(bucket_path(project_id, "/dock/tools/#{tool_id}/position.json"))
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Changes the position of a tool on the project dock.
|
|
77
|
+
#
|
|
78
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
79
|
+
# @param tool_id [Integer, String] tool ID
|
|
80
|
+
# @param position [Integer] new position (1-based, 1 = first on dock)
|
|
81
|
+
# @return [void]
|
|
82
|
+
def reposition(project_id:, tool_id:, position:)
|
|
83
|
+
http_put(bucket_path(project_id, "/dock/tools/#{tool_id}/position.json"),
|
|
84
|
+
body: { position: position })
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for upload operations.
|
|
6
|
+
#
|
|
7
|
+
# Uploads are files stored within vaults. They are created from
|
|
8
|
+
# attachments (via attachable_sgid) and can have descriptions
|
|
9
|
+
# and version history.
|
|
10
|
+
#
|
|
11
|
+
# @example List uploads in a vault
|
|
12
|
+
# account.uploads.list(project_id: 123, vault_id: 456).each do |upload|
|
|
13
|
+
# puts "#{upload["filename"]} - #{upload["byte_size"]} bytes"
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# @example Create an upload
|
|
17
|
+
# upload = account.uploads.create(
|
|
18
|
+
# project_id: 123,
|
|
19
|
+
# vault_id: 456,
|
|
20
|
+
# attachable_sgid: attachment_sgid,
|
|
21
|
+
# description: "Q4 financial report"
|
|
22
|
+
# )
|
|
23
|
+
class UploadsService < BaseService
|
|
24
|
+
# Lists all uploads in a vault.
|
|
25
|
+
#
|
|
26
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
27
|
+
# @param vault_id [Integer, String] vault ID
|
|
28
|
+
# @return [Enumerator<Hash>] uploads
|
|
29
|
+
def list(project_id:, vault_id:)
|
|
30
|
+
paginate(bucket_path(project_id, "/vaults/#{vault_id}/uploads.json"))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Gets a specific upload.
|
|
34
|
+
#
|
|
35
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
36
|
+
# @param upload_id [Integer, String] upload ID
|
|
37
|
+
# @return [Hash] upload data
|
|
38
|
+
def get(project_id:, upload_id:)
|
|
39
|
+
http_get(bucket_path(project_id, "/uploads/#{upload_id}")).json
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Creates a new upload in a vault.
|
|
43
|
+
#
|
|
44
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
45
|
+
# @param vault_id [Integer, String] vault ID
|
|
46
|
+
# @param attachable_sgid [String] signed global ID from attachment upload
|
|
47
|
+
# @param description [String, nil] upload description in HTML
|
|
48
|
+
# @param base_name [String, nil] filename without extension
|
|
49
|
+
# @return [Hash] created upload
|
|
50
|
+
def create(project_id:, vault_id:, attachable_sgid:, description: nil, base_name: nil)
|
|
51
|
+
body = compact_params(
|
|
52
|
+
attachable_sgid: attachable_sgid,
|
|
53
|
+
description: description,
|
|
54
|
+
base_name: base_name
|
|
55
|
+
)
|
|
56
|
+
http_post(bucket_path(project_id, "/vaults/#{vault_id}/uploads.json"), body: body).json
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Updates an existing upload.
|
|
60
|
+
#
|
|
61
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
62
|
+
# @param upload_id [Integer, String] upload ID
|
|
63
|
+
# @param description [String, nil] new description
|
|
64
|
+
# @param base_name [String, nil] new filename without extension
|
|
65
|
+
# @return [Hash] updated upload
|
|
66
|
+
def update(project_id:, upload_id:, description: nil, base_name: nil)
|
|
67
|
+
body = compact_params(
|
|
68
|
+
description: description,
|
|
69
|
+
base_name: base_name
|
|
70
|
+
)
|
|
71
|
+
http_put(bucket_path(project_id, "/uploads/#{upload_id}"), body: body).json
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Lists all versions of an upload.
|
|
75
|
+
#
|
|
76
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
77
|
+
# @param upload_id [Integer, String] upload ID
|
|
78
|
+
# @return [Enumerator<Hash>] upload versions
|
|
79
|
+
def list_versions(project_id:, upload_id:)
|
|
80
|
+
paginate(bucket_path(project_id, "/uploads/#{upload_id}/versions.json"))
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for vault (folder) operations.
|
|
6
|
+
#
|
|
7
|
+
# Vaults are folders in the Files & Documents tool. They can contain
|
|
8
|
+
# documents, uploads (files), and nested vaults (subfolders).
|
|
9
|
+
#
|
|
10
|
+
# @example Get a vault
|
|
11
|
+
# vault = account.vaults.get(project_id: 123, vault_id: 456)
|
|
12
|
+
#
|
|
13
|
+
# @example List subfolders
|
|
14
|
+
# account.vaults.list(project_id: 123, vault_id: 456).each do |folder|
|
|
15
|
+
# puts folder["title"]
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# @example Create a subfolder
|
|
19
|
+
# vault = account.vaults.create(
|
|
20
|
+
# project_id: 123,
|
|
21
|
+
# vault_id: 456,
|
|
22
|
+
# title: "2024 Reports"
|
|
23
|
+
# )
|
|
24
|
+
class VaultsService < BaseService
|
|
25
|
+
# Gets a specific vault.
|
|
26
|
+
#
|
|
27
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
28
|
+
# @param vault_id [Integer, String] vault ID
|
|
29
|
+
# @return [Hash] vault data
|
|
30
|
+
def get(project_id:, vault_id:)
|
|
31
|
+
http_get(bucket_path(project_id, "/vaults/#{vault_id}")).json
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Lists all child vaults (subfolders) in a vault.
|
|
35
|
+
#
|
|
36
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
37
|
+
# @param vault_id [Integer, String] parent vault ID
|
|
38
|
+
# @return [Enumerator<Hash>] child vaults
|
|
39
|
+
def list(project_id:, vault_id:)
|
|
40
|
+
paginate(bucket_path(project_id, "/vaults/#{vault_id}/vaults.json"))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Creates a new child vault (subfolder).
|
|
44
|
+
#
|
|
45
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
46
|
+
# @param vault_id [Integer, String] parent vault ID
|
|
47
|
+
# @param title [String] vault name
|
|
48
|
+
# @return [Hash] created vault
|
|
49
|
+
def create(project_id:, vault_id:, title:)
|
|
50
|
+
body = { title: title }
|
|
51
|
+
http_post(bucket_path(project_id, "/vaults/#{vault_id}/vaults.json"), body: body).json
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Updates an existing vault.
|
|
55
|
+
#
|
|
56
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
57
|
+
# @param vault_id [Integer, String] vault ID
|
|
58
|
+
# @param title [String, nil] new title
|
|
59
|
+
# @return [Hash] updated vault
|
|
60
|
+
def update(project_id:, vault_id:, title: nil)
|
|
61
|
+
body = compact_params(title: title)
|
|
62
|
+
http_put(bucket_path(project_id, "/vaults/#{vault_id}"), body: body).json
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Lists all documents in a vault.
|
|
66
|
+
#
|
|
67
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
68
|
+
# @param vault_id [Integer, String] vault ID
|
|
69
|
+
# @return [Enumerator<Hash>] documents
|
|
70
|
+
def list_documents(project_id:, vault_id:)
|
|
71
|
+
paginate(bucket_path(project_id, "/vaults/#{vault_id}/documents.json"))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Lists all uploads (files) in a vault.
|
|
75
|
+
#
|
|
76
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
77
|
+
# @param vault_id [Integer, String] vault ID
|
|
78
|
+
# @return [Enumerator<Hash>] uploads
|
|
79
|
+
def list_uploads(project_id:, vault_id:)
|
|
80
|
+
paginate(bucket_path(project_id, "/vaults/#{vault_id}/uploads.json"))
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for webhook operations.
|
|
6
|
+
#
|
|
7
|
+
# Webhooks allow external services to receive notifications when events
|
|
8
|
+
# occur in a Basecamp project.
|
|
9
|
+
#
|
|
10
|
+
# @example List webhooks
|
|
11
|
+
# account.webhooks.list(project_id: 123).each do |webhook|
|
|
12
|
+
# puts "#{webhook["payload_url"]} - #{webhook["active"]}"
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# @example Create a webhook
|
|
16
|
+
# webhook = account.webhooks.create(
|
|
17
|
+
# project_id: 123,
|
|
18
|
+
# payload_url: "https://example.com/webhooks/basecamp",
|
|
19
|
+
# types: ["Todo", "Message"]
|
|
20
|
+
# )
|
|
21
|
+
class WebhooksService < BaseService
|
|
22
|
+
# Lists all webhooks in a project.
|
|
23
|
+
#
|
|
24
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
25
|
+
# @return [Enumerator<Hash>] webhooks
|
|
26
|
+
def list(project_id:)
|
|
27
|
+
paginate(bucket_path(project_id, "/webhooks.json"))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Gets a specific webhook.
|
|
31
|
+
#
|
|
32
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
33
|
+
# @param webhook_id [Integer, String] webhook ID
|
|
34
|
+
# @return [Hash] webhook data
|
|
35
|
+
def get(project_id:, webhook_id:)
|
|
36
|
+
http_get(bucket_path(project_id, "/webhooks/#{webhook_id}")).json
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Creates a new webhook.
|
|
40
|
+
#
|
|
41
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
42
|
+
# @param payload_url [String] URL to receive webhook payloads
|
|
43
|
+
# @param types [Array<String>, nil] recording types to subscribe to
|
|
44
|
+
# @return [Hash] created webhook
|
|
45
|
+
def create(project_id:, payload_url:, types: nil)
|
|
46
|
+
raise Basecamp::UsageError.new("Webhook payload URL is required") if payload_url.to_s.empty?
|
|
47
|
+
Basecamp::Security.require_https!(payload_url, "webhook payload URL")
|
|
48
|
+
|
|
49
|
+
body = compact_params(
|
|
50
|
+
payload_url: payload_url,
|
|
51
|
+
types: types
|
|
52
|
+
)
|
|
53
|
+
http_post(bucket_path(project_id, "/webhooks.json"), body: body).json
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Updates an existing webhook.
|
|
57
|
+
#
|
|
58
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
59
|
+
# @param webhook_id [Integer, String] webhook ID
|
|
60
|
+
# @param payload_url [String, nil] new URL
|
|
61
|
+
# @param types [Array<String>, nil] new recording types
|
|
62
|
+
# @param active [Boolean, nil] whether the webhook is active
|
|
63
|
+
# @return [Hash] updated webhook
|
|
64
|
+
def update(project_id:, webhook_id:, payload_url: nil, types: nil, active: nil)
|
|
65
|
+
if payload_url
|
|
66
|
+
Basecamp::Security.require_https!(payload_url, "webhook payload URL")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
body = compact_params(
|
|
70
|
+
payload_url: payload_url,
|
|
71
|
+
types: types,
|
|
72
|
+
active: active
|
|
73
|
+
)
|
|
74
|
+
http_put(bucket_path(project_id, "/webhooks/#{webhook_id}"), body: body).json
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Deletes a webhook.
|
|
78
|
+
#
|
|
79
|
+
# @param project_id [Integer, String] project (bucket) ID
|
|
80
|
+
# @param webhook_id [Integer, String] webhook ID
|
|
81
|
+
# @return [void]
|
|
82
|
+
def delete(project_id:, webhook_id:)
|
|
83
|
+
http_delete(bucket_path(project_id, "/webhooks/#{webhook_id}"))
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
# A simple token provider that returns a static access token.
|
|
5
|
+
# Useful for testing or when you manage token refresh externally.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# provider = Basecamp::StaticTokenProvider.new(ENV["BASECAMP_ACCESS_TOKEN"])
|
|
9
|
+
class StaticTokenProvider
|
|
10
|
+
include TokenProvider
|
|
11
|
+
|
|
12
|
+
# @param token [String] the static access token
|
|
13
|
+
def initialize(token)
|
|
14
|
+
raise ArgumentError, "token cannot be nil or empty" if token.nil? || token.empty?
|
|
15
|
+
|
|
16
|
+
@token = token
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [String] the access token
|
|
20
|
+
def access_token
|
|
21
|
+
@token
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
# Interface for providing OAuth access tokens.
|
|
5
|
+
# Implement this to provide custom token management (e.g., refresh tokens).
|
|
6
|
+
#
|
|
7
|
+
# @example Static token provider
|
|
8
|
+
# token_provider = Basecamp::StaticTokenProvider.new("your-access-token")
|
|
9
|
+
# client = Basecamp::Client.new(config: config, token_provider: token_provider)
|
|
10
|
+
#
|
|
11
|
+
# @example Custom token provider with refresh
|
|
12
|
+
# class MyTokenProvider
|
|
13
|
+
# include Basecamp::TokenProvider
|
|
14
|
+
#
|
|
15
|
+
# def access_token
|
|
16
|
+
# # Return current token, refreshing if needed
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# def refresh
|
|
20
|
+
# # Refresh the token
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
module TokenProvider
|
|
24
|
+
# Returns the current access token.
|
|
25
|
+
# @return [String] the OAuth access token
|
|
26
|
+
def access_token
|
|
27
|
+
raise NotImplementedError, "#{self.class} must implement #access_token"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Refreshes the access token.
|
|
31
|
+
# @return [Boolean] true if refresh succeeded
|
|
32
|
+
def refresh
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns whether token refresh is supported.
|
|
37
|
+
# @return [Boolean]
|
|
38
|
+
def refreshable?
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Webhooks
|
|
5
|
+
# Structured wrapper around webhook event payloads.
|
|
6
|
+
# Accepts any hash - does not reject unknown fields or event kinds.
|
|
7
|
+
class Event
|
|
8
|
+
attr_reader :id, :kind, :details, :created_at, :recording, :creator, :copy, :raw
|
|
9
|
+
|
|
10
|
+
def initialize(hash)
|
|
11
|
+
@raw = hash
|
|
12
|
+
@id = hash["id"]
|
|
13
|
+
@kind = hash["kind"]
|
|
14
|
+
@details = hash["details"] || {}
|
|
15
|
+
@created_at = hash["created_at"]
|
|
16
|
+
@recording = hash["recording"] || {}
|
|
17
|
+
@creator = hash["creator"] || {}
|
|
18
|
+
@copy = hash["copy"]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Parse "todo_created" -> { type: "todo", action: "created" }
|
|
22
|
+
def parsed_kind
|
|
23
|
+
return { type: kind, action: "" } unless kind&.include?("_")
|
|
24
|
+
last_underscore = kind.rindex("_")
|
|
25
|
+
{
|
|
26
|
+
type: kind[0...last_underscore],
|
|
27
|
+
action: kind[(last_underscore + 1)..]
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Known webhook recording types (convenience constants, not exhaustive).
|
|
33
|
+
module RecordingType
|
|
34
|
+
CHECKIN_REPLY = "Checkin::Reply"
|
|
35
|
+
CLOUD_FILE = "CloudFile"
|
|
36
|
+
COMMENT = "Comment"
|
|
37
|
+
DOCUMENT = "Document"
|
|
38
|
+
FORWARD_REPLY = "Forward::Reply"
|
|
39
|
+
GOOGLE_DOCUMENT = "GoogleDocument"
|
|
40
|
+
INBOX_FORWARD = "Inbox::Forward"
|
|
41
|
+
MESSAGE = "Message"
|
|
42
|
+
QUESTION = "Question"
|
|
43
|
+
QUESTION_ANSWER = "Question::Answer"
|
|
44
|
+
SCHEDULE_ENTRY = "Schedule::Entry"
|
|
45
|
+
TODO = "Todo"
|
|
46
|
+
TODOLIST = "Todolist"
|
|
47
|
+
TODOLIST_GROUP = "Todolist::Group"
|
|
48
|
+
UPLOAD = "Upload"
|
|
49
|
+
VAULT = "Vault"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|