activeproject 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +304 -0
- data/Rakefile +3 -0
- data/lib/active_project/adapters/base.rb +134 -0
- data/lib/active_project/adapters/basecamp_adapter.rb +577 -0
- data/lib/active_project/adapters/jira_adapter.rb +637 -0
- data/lib/active_project/adapters/trello_adapter.rb +535 -0
- data/lib/active_project/association_proxy.rb +142 -0
- data/lib/active_project/configuration.rb +59 -0
- data/lib/active_project/configurations/base_adapter_configuration.rb +32 -0
- data/lib/active_project/configurations/trello_configuration.rb +31 -0
- data/lib/active_project/errors.rb +40 -0
- data/lib/active_project/resource_factory.rb +130 -0
- data/lib/active_project/resources/base_resource.rb +69 -0
- data/lib/active_project/resources/comment.rb +16 -0
- data/lib/active_project/resources/issue.rb +41 -0
- data/lib/active_project/resources/project.rb +20 -0
- data/lib/active_project/resources/user.rb +13 -0
- data/lib/active_project/version.rb +3 -0
- data/lib/active_project/webhook_event.rb +20 -0
- data/lib/activeproject.rb +61 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8f3323a59c15ce296050ec1c7b231416d2cac1aa6f78fe133c8d7d2fd3ebb639
|
4
|
+
data.tar.gz: 0f60e5b29d28e3adca3ce7e26df63929ed3c0d4b898887213823cd284efa365a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 756f9f83b0f443ceb6d426514d79c7c7cca5c8f6b410ab6fbad9469426354735d706d0b00be102a1f36ec4dcb4a7ee04d8b110c6aaaeda3ac8ccce2b61695623
|
7
|
+
data.tar.gz: 5397faf8bae3c5fe4ab86a98360de55c92237270806ac3372cdf7cff08742fa857ccff9f4a139998116ce366cd321df5afab6d81368a78970822de853d43c68c
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright Abdelkader Boudih
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,304 @@
|
|
1
|
+
# ActiveProject Gem
|
2
|
+
|
3
|
+
A standardized Ruby interface for multiple project management APIs (Jira, Basecamp, Trello, etc.).
|
4
|
+
|
5
|
+
## Problem
|
6
|
+
|
7
|
+
Integrating with various project management platforms like Jira, Basecamp, and Trello often requires writing separate, complex clients for each API. Developers face challenges in handling different authentication methods, error formats, data structures, and workflow concepts across these platforms.
|
8
|
+
|
9
|
+
## Solution
|
10
|
+
|
11
|
+
The ActiveProject gem aims to solve this by providing a unified, opinionated interface built on the **Adapter pattern**. It abstracts away the complexities of individual APIs, offering:
|
12
|
+
|
13
|
+
* **Normalized Data Models:** Common Ruby objects for core concepts like `Project`, `Task` (Issue/Card/To-do), `Comment`, and `User`.
|
14
|
+
* **Standardized Operations:** Consistent methods for creating, reading, updating, and transitioning tasks (e.g., `task.close!`, `task.reopen!`).
|
15
|
+
* **Unified Error Handling:** A common set of exceptions (`AuthenticationError`, `NotFoundError`, `RateLimitError`, etc.) regardless of the underlying platform.
|
16
|
+
|
17
|
+
## Supported Platforms
|
18
|
+
|
19
|
+
The initial focus is on integrating with platforms primarily via their **REST APIs**:
|
20
|
+
|
21
|
+
* **Jira (Cloud & Server):** REST API (v2/v3)
|
22
|
+
* **Basecamp (v3+):** REST API
|
23
|
+
* **Trello:** REST API
|
24
|
+
|
25
|
+
Future integrations might include platforms like Asana (REST), Monday.com (GraphQL), and Linear (GraphQL). For GraphQL-based APIs, the adapter will encapsulate the query logic, maintaining a consistent interface for the gem user.
|
26
|
+
|
27
|
+
## Core Concepts
|
28
|
+
|
29
|
+
* **Project:** Represents a Jira Project, Basecamp Project, or Trello Board.
|
30
|
+
* **Task:** A unified representation of a Jira Issue, Basecamp To-do, or Trello Card. Includes normalized fields like `title`, `description`, `assignees`, `status`, and `priority`.
|
31
|
+
* **Status Normalization:** Maps platform-specific statuses (Jira statuses, Basecamp completion, Trello lists) to a common set like `:open`, `:in_progress`, `:closed`.
|
32
|
+
* **Priority Normalization:** Maps priorities (where available, like in Jira) to a standard scale (e.g., `:low`, `:medium`, `:high`).
|
33
|
+
|
34
|
+
## Architecture
|
35
|
+
|
36
|
+
The gem uses an **Adapter pattern**, with specific adapters (`Adapters::JiraAdapter`, `Adapters::BasecampAdapter`, etc.) implementing a common interface. This allows for easy extension to new platforms.
|
37
|
+
|
38
|
+
## Planned Features
|
39
|
+
|
40
|
+
* CRUD operations for Projects and Tasks.
|
41
|
+
* Unified status transitions.
|
42
|
+
* Comment management.
|
43
|
+
* Standardized error handling and reporting.
|
44
|
+
* Webhook support for real-time updates from platforms.
|
45
|
+
* Configuration management for API credentials.
|
46
|
+
* Utilization of **Mermaid diagrams** to visualize workflows and integration logic within documentation.
|
47
|
+
|
48
|
+
## Installation
|
49
|
+
|
50
|
+
Add this line to your application's Gemfile:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
gem 'activeproject'
|
54
|
+
```
|
55
|
+
|
56
|
+
And then execute:
|
57
|
+
|
58
|
+
```bash
|
59
|
+
$ bundle install
|
60
|
+
```
|
61
|
+
|
62
|
+
Or install it yourself as:
|
63
|
+
|
64
|
+
```bash
|
65
|
+
$ gem install activeproject
|
66
|
+
```
|
67
|
+
|
68
|
+
## Usage
|
69
|
+
|
70
|
+
### Configuration
|
71
|
+
|
72
|
+
Configure the adapters in an initializer (e.g., `config/initializers/active_project.rb`):
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
ActiveProject.configure do |config|
|
76
|
+
# Configure Jira Adapter
|
77
|
+
config.add_adapter(:jira,
|
78
|
+
site_url: ENV.fetch('JIRA_SITE_URL'),
|
79
|
+
username: ENV.fetch('JIRA_USERNAME'), # Your Jira email
|
80
|
+
api_token: ENV.fetch('JIRA_API_TOKEN')
|
81
|
+
)
|
82
|
+
|
83
|
+
|
84
|
+
# Configure Basecamp Adapter
|
85
|
+
config.add_adapter(:basecamp,
|
86
|
+
account_id: ENV.fetch('BASECAMP_ACCOUNT_ID'),
|
87
|
+
access_token: ENV.fetch('BASECAMP_ACCESS_TOKEN')
|
88
|
+
)
|
89
|
+
|
90
|
+
# Configure other adapters later (e.g., :trello, :basecamp)
|
91
|
+
# config.add_adapter(:trello, key: '...', token: '...')
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
### Basic Usage (Jira Example)
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
# Get the configured Jira adapter instance
|
99
|
+
jira_adapter = ActiveProject.adapter(:jira)
|
100
|
+
|
101
|
+
begin
|
102
|
+
# List projects
|
103
|
+
projects = jira_adapter.list_projects
|
104
|
+
puts "Found #{projects.count} projects."
|
105
|
+
first_project = projects.first
|
106
|
+
|
107
|
+
if first_project
|
108
|
+
puts "Listing issues for project: #{first_project.key}"
|
109
|
+
# List issues in the first project
|
110
|
+
issues = jira_adapter.list_issues(first_project.key, max_results: 5)
|
111
|
+
puts "- Found #{issues.count} issues (showing max 5)."
|
112
|
+
|
113
|
+
# Find a specific issue (replace 'PROJ-1' with a valid key)
|
114
|
+
# issue_key_to_find = 'PROJ-1'
|
115
|
+
# issue = jira_adapter.find_issue(issue_key_to_find)
|
116
|
+
# puts "- Found issue: #{issue.key} - #{issue.title}"
|
117
|
+
|
118
|
+
# Create a new issue
|
119
|
+
puts "Creating a new issue..."
|
120
|
+
new_issue_attributes = {
|
121
|
+
project: { key: first_project.key },
|
122
|
+
summary: "New task from ActiveProject Gem #{Time.now}",
|
123
|
+
issue_type: { name: 'Task' }, # Ensure 'Task' is a valid issue type name
|
124
|
+
description: "This issue was created via the ActiveProject gem."
|
125
|
+
}
|
126
|
+
created_issue = jira_adapter.create_issue(first_project.key, new_issue_attributes)
|
127
|
+
puts "- Created issue: #{created_issue.key} - #{created_issue.title}"
|
128
|
+
|
129
|
+
# Update the issue
|
130
|
+
puts "Updating issue #{created_issue.key}..."
|
131
|
+
updated_issue = jira_adapter.update_issue(created_issue.key, { summary: "[Updated] #{created_issue.title}" })
|
132
|
+
puts "- Updated summary: #{updated_issue.title}"
|
133
|
+
|
134
|
+
# Add a comment
|
135
|
+
puts "Adding comment to issue #{updated_issue.key}..."
|
136
|
+
comment = jira_adapter.add_comment(updated_issue.key, "This is a comment added via the ActiveProject gem.")
|
137
|
+
puts "- Comment added with ID: #{comment.id}"
|
138
|
+
end
|
139
|
+
|
140
|
+
rescue ActiveProject::AuthenticationError => e
|
141
|
+
puts "Error: Jira Authentication Failed - #{e.message}"
|
142
|
+
rescue ActiveProject::NotFoundError => e
|
143
|
+
puts "Error: Resource Not Found - #{e.message}"
|
144
|
+
rescue ActiveProject::ValidationError => e
|
145
|
+
puts "Error: Validation Failed - #{e.message} (Details: #{e.errors})"
|
146
|
+
rescue ActiveProject::ApiError => e
|
147
|
+
puts "Error: Jira API Error (#{e.status_code}) - #{e.message}"
|
148
|
+
rescue => e
|
149
|
+
puts "An unexpected error occurred: #{e.message}"
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
### Basic Usage (Basecamp Example)
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
# Get the configured Basecamp adapter instance
|
157
|
+
basecamp_config = ActiveProject.configuration.adapter_config(:basecamp)
|
158
|
+
basecamp_adapter = ActiveProject::Adapters::BasecampAdapter.new(**basecamp_config)
|
159
|
+
|
160
|
+
begin
|
161
|
+
# List projects
|
162
|
+
projects = basecamp_adapter.list_projects
|
163
|
+
puts "Found #{projects.count} Basecamp projects."
|
164
|
+
first_project = projects.first
|
165
|
+
|
166
|
+
if first_project
|
167
|
+
puts "Listing issues (To-dos) for project: #{first_project.name} (ID: #{first_project.id})"
|
168
|
+
# List issues (To-dos) in the first project
|
169
|
+
# Note: This lists across all to-do lists in the project
|
170
|
+
issues = basecamp_adapter.list_issues(first_project.id)
|
171
|
+
puts "- Found #{issues.count} To-dos."
|
172
|
+
|
173
|
+
# Create a new issue (To-do)
|
174
|
+
# IMPORTANT: You need a valid todolist_id for the target project.
|
175
|
+
# You might need another API call to find a todolist_id first.
|
176
|
+
# todolist_id_for_test = 1234567 # Replace with a real ID
|
177
|
+
# puts "Creating a new To-do..."
|
178
|
+
# new_issue_attributes = {
|
179
|
+
# todolist_id: todolist_id_for_test,
|
180
|
+
# title: "New BC To-do from ActiveProject Gem #{Time.now}",
|
181
|
+
# description: "<em>HTML description</em> for the to-do."
|
182
|
+
# }
|
183
|
+
# created_issue = basecamp_adapter.create_issue(first_project.id, new_issue_attributes)
|
184
|
+
# puts "- Created To-do: #{created_issue.id} - #{created_issue.title}"
|
185
|
+
|
186
|
+
# --- Operations requiring project_id context (Currently raise NotImplementedError) ---
|
187
|
+
# puts "Finding, updating, and commenting require project_id context and are currently not directly usable via the base interface."
|
188
|
+
#
|
189
|
+
# # Find a specific issue (To-do) - Requires project_id context
|
190
|
+
# # todo_id_to_find = created_issue.id
|
191
|
+
# # issue = basecamp_adapter.find_issue(todo_id_to_find) # Raises NotImplementedError
|
192
|
+
#
|
193
|
+
# # Update the issue (To-do) - Requires project_id context
|
194
|
+
# # updated_issue = basecamp_adapter.update_issue(created_issue.id, { title: "[Updated] #{created_issue.title}" }) # Raises NotImplementedError
|
195
|
+
#
|
196
|
+
# # Add a comment - Requires project_id context
|
197
|
+
# # comment = basecamp_adapter.add_comment(created_issue.id, "This is an <b>HTML</b> comment.") # Raises NotImplementedError
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
rescue ActiveProject::AuthenticationError => e
|
202
|
+
puts "Error: Basecamp Authentication Failed - #{e.message}"
|
203
|
+
rescue ActiveProject::NotFoundError => e
|
204
|
+
puts "Error: Basecamp Resource Not Found - #{e.message}"
|
205
|
+
rescue ActiveProject::ValidationError => e
|
206
|
+
puts "Error: Basecamp Validation Failed - #{e.message}"
|
207
|
+
rescue ActiveProject::RateLimitError => e
|
208
|
+
puts "Error: Basecamp Rate Limit Exceeded - #{e.message}"
|
209
|
+
rescue ActiveProject::ApiError => e
|
210
|
+
puts "Error: Basecamp API Error (#{e.status_code}) - #{e.message}"
|
211
|
+
rescue NotImplementedError => e
|
212
|
+
puts "Error: Method requires project_id context which is not yet implemented: #{e.message}"
|
213
|
+
rescue => e
|
214
|
+
puts "An unexpected error occurred: #{e.message}"
|
215
|
+
end
|
216
|
+
```
|
217
|
+
|
218
|
+
|
219
|
+
|
220
|
+
### Webhook Handling
|
221
|
+
|
222
|
+
The gem provides helpers for parsing webhook payloads and verifying signatures (where applicable), but you need to implement your own webhook receiver endpoint (e.g., a Rails controller action).
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
# Example Rails Controller Action
|
226
|
+
class WebhooksController < ApplicationController
|
227
|
+
# Disable CSRF protection for webhook endpoints
|
228
|
+
skip_before_action :verify_authenticity_token
|
229
|
+
|
230
|
+
def jira_webhook
|
231
|
+
adapter = ActiveProject.adapter(:jira)
|
232
|
+
request_body = request.body.read
|
233
|
+
|
234
|
+
# Verification (if applicable and implemented for your Jira setup)
|
235
|
+
# signature = request.headers['X-Jira-Signature'] # Example header
|
236
|
+
# unless adapter.verify_webhook_signature(request_body, signature)
|
237
|
+
# render plain: 'Invalid signature', status: :unauthorized
|
238
|
+
# return
|
239
|
+
# end
|
240
|
+
|
241
|
+
# Parse the event
|
242
|
+
event = adapter.parse_webhook(request_body, request.headers)
|
243
|
+
|
244
|
+
if event
|
245
|
+
puts "Received Jira Event: #{event.event_type} for #{event.object_kind} #{event.object_key || event.object_id}"
|
246
|
+
# Process the event (e.g., queue a background job)
|
247
|
+
# handle_event(event)
|
248
|
+
else
|
249
|
+
puts "Received unhandled or unparseable Jira webhook"
|
250
|
+
end
|
251
|
+
|
252
|
+
head :ok # Respond to Jira quickly
|
253
|
+
end
|
254
|
+
|
255
|
+
def trello_webhook
|
256
|
+
adapter = ActiveProject.adapter(:trello)
|
257
|
+
request_body = request.body.read
|
258
|
+
signature = request.headers['X-Trello-Webhook'] # Trello signature header
|
259
|
+
callback_url = request.original_url # The URL Trello sent the webhook to
|
260
|
+
trello_api_secret = ENV.fetch('TRELLO_API_SECRET') # You need your secret
|
261
|
+
|
262
|
+
# Verification (Manual comparison using the helper)
|
263
|
+
expected_signature = ActiveProject::Adapters::TrelloAdapter.compute_webhook_signature(
|
264
|
+
callback_url,
|
265
|
+
request_body,
|
266
|
+
trello_api_secret
|
267
|
+
)
|
268
|
+
|
269
|
+
unless ActiveSupport::SecurityUtils.secure_compare(signature, expected_signature)
|
270
|
+
render plain: 'Invalid Trello signature', status: :unauthorized
|
271
|
+
return
|
272
|
+
end
|
273
|
+
|
274
|
+
# Parse the event
|
275
|
+
event = adapter.parse_webhook(request_body)
|
276
|
+
|
277
|
+
if event
|
278
|
+
puts "Received Trello Event: #{event.event_type} for #{event.object_kind} #{event.object_id}"
|
279
|
+
# Process the event
|
280
|
+
else
|
281
|
+
puts "Received unhandled or unparseable Trello webhook"
|
282
|
+
end
|
283
|
+
|
284
|
+
head :ok
|
285
|
+
end
|
286
|
+
|
287
|
+
# Add similar actions for other adapters like Basecamp
|
288
|
+
|
289
|
+
end
|
290
|
+
```
|
291
|
+
|
292
|
+
## Development
|
293
|
+
|
294
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
295
|
+
|
296
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `lib/active_project/version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [RubyGems.org](https://rubygems.org).
|
297
|
+
|
298
|
+
## Contributing
|
299
|
+
|
300
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/seuros/activeproject.
|
301
|
+
|
302
|
+
## License
|
303
|
+
|
304
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
module Adapters
|
5
|
+
# Base abstract class defining the interface for all adapters.
|
6
|
+
# Concrete adapters should inherit from this class and implement its abstract methods.
|
7
|
+
class Base
|
8
|
+
# Lists projects accessible by the configured credentials.
|
9
|
+
# @return [Array<ActiveProject::Project>]
|
10
|
+
def list_projects
|
11
|
+
raise NotImplementedError, "#{self.class.name} must implement #list_projects"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Finds a specific project by its ID or key.
|
15
|
+
# @param id [String, Integer] The ID or key of the project.
|
16
|
+
# @return [ActiveProject::Project, nil] The project object or nil if not found.
|
17
|
+
def find_project(id)
|
18
|
+
raise NotImplementedError, "#{self.class.name} must implement #find_project"
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# Creates a new project.
|
23
|
+
# @param attributes [Hash] Project attributes (platform-specific).
|
24
|
+
# @return [ActiveProject::Project] The created project object.
|
25
|
+
def create_project(attributes)
|
26
|
+
raise NotImplementedError, "#{self.class.name} must implement #create_project"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates a new list/container within a project (e.g., Trello List, Basecamp Todolist).
|
30
|
+
# Not applicable to all platforms (e.g., Jira statuses are managed differently).
|
31
|
+
# @param project_id [String, Integer] The ID or key of the parent project.
|
32
|
+
# @param attributes [Hash] List attributes (platform-specific, e.g., :name).
|
33
|
+
# @return [Hash] A hash representing the created list (platform-specific structure).
|
34
|
+
def create_list(project_id, attributes)
|
35
|
+
raise NotImplementedError, "#{self.class.name} does not support #create_list or must implement #create_list"
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# Deletes a project. Use with caution.
|
40
|
+
# @param project_id [String, Integer] The ID or key of the project to delete.
|
41
|
+
# @return [Boolean] true if deletion was successful (or accepted), false otherwise.
|
42
|
+
# @raise [NotImplementedError] if deletion is not supported or implemented.
|
43
|
+
def delete_project(project_id)
|
44
|
+
raise NotImplementedError, "#{self.class.name} does not support #delete_project or must implement it"
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Lists issues within a specific project.
|
49
|
+
# @param project_id [String, Integer] The ID or key of the project.
|
50
|
+
# @param options [Hash] Optional filtering/pagination options.
|
51
|
+
# @return [Array<ActiveProject::Issue>]
|
52
|
+
def list_issues(project_id, options = {})
|
53
|
+
raise NotImplementedError, "#{self.class.name} must implement #list_issues"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Finds a specific issue by its ID or key.
|
57
|
+
# @param id [String, Integer] The ID or key of the issue.
|
58
|
+
# @param context [Hash] Optional context hash (e.g., { project_id: '...' } for Basecamp).
|
59
|
+
# @return [ActiveProject::Issue, nil] The issue object or nil if not found.
|
60
|
+
def find_issue(id, context = {})
|
61
|
+
raise NotImplementedError, "#{self.class.name} must implement #find_issue"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Creates a new issue.
|
65
|
+
# @param project_id [String, Integer] The ID or key of the project to create the issue in.
|
66
|
+
# @param attributes [Hash] Issue attributes (e.g., title, description).
|
67
|
+
# @return [ActiveProject::Issue] The created issue object.
|
68
|
+
def create_issue(project_id, attributes)
|
69
|
+
raise NotImplementedError, "#{self.class.name} must implement #create_issue"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Updates an existing issue.
|
73
|
+
# @param id [String, Integer] The ID or key of the issue to update.
|
74
|
+
# @param attributes [Hash] Issue attributes to update.
|
75
|
+
# @param context [Hash] Optional context hash (e.g., { project_id: '...' } for Basecamp).
|
76
|
+
# @return [ActiveProject::Issue] The updated issue object.
|
77
|
+
def update_issue(id, attributes, context = {})
|
78
|
+
raise NotImplementedError, "#{self.class.name} must implement #update_issue"
|
79
|
+
end
|
80
|
+
|
81
|
+
# Adds a comment to an issue.
|
82
|
+
# @param issue_id [String, Integer] The ID or key of the issue.
|
83
|
+
# @param comment_body [String] The text of the comment.
|
84
|
+
# @param context [Hash] Optional context hash (e.g., { project_id: '...' } for Basecamp).
|
85
|
+
# @return [ActiveProject::Comment] The created comment object.
|
86
|
+
def add_comment(issue_id, comment_body, context = {})
|
87
|
+
raise NotImplementedError, "#{self.class.name} must implement #add_comment"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Verifies the signature of an incoming webhook request, if supported by the platform.
|
91
|
+
# @param request_body [String] The raw request body.
|
92
|
+
# @param signature_header [String] The value of the platform-specific signature header (e.g., 'X-Trello-Webhook').
|
93
|
+
# @return [Boolean] true if the signature is valid or verification is not supported/needed, false otherwise.
|
94
|
+
# @raise [NotImplementedError] if verification is applicable but not implemented by a subclass.
|
95
|
+
def verify_webhook_signature(request_body, signature_header)
|
96
|
+
# Default implementation assumes no verification needed or supported.
|
97
|
+
# Adapters supporting verification should override this.
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
# Parses an incoming webhook payload into a standardized WebhookEvent struct.
|
102
|
+
# @param request_body [String] The raw request body.
|
103
|
+
# @param headers [Hash] Optional hash of request headers (may be needed for event type detection).
|
104
|
+
# @return [ActiveProject::WebhookEvent, nil] The parsed event object or nil if the payload is irrelevant/unparseable.
|
105
|
+
# @raise [NotImplementedError] if webhook parsing is not implemented for the adapter.
|
106
|
+
def parse_webhook(request_body, headers = {})
|
107
|
+
raise NotImplementedError, "#{self.class.name} must implement #parse_webhook"
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
# Retrieves details for the currently authenticated user.
|
112
|
+
# @return [ActiveProject::Resources::User] The user object.
|
113
|
+
# @raise [ActiveProject::AuthenticationError] if authentication fails.
|
114
|
+
# @raise [ActiveProject::ApiError] for other API-related errors.
|
115
|
+
def get_current_user
|
116
|
+
raise NotImplementedError, "#{self.class.name} must implement #get_current_user"
|
117
|
+
end
|
118
|
+
|
119
|
+
# Checks if the adapter can successfully authenticate and connect to the service.
|
120
|
+
# Typically calls #get_current_user internally and catches authentication errors.
|
121
|
+
# @return [Boolean] true if connection is successful, false otherwise.
|
122
|
+
def connected?
|
123
|
+
raise NotImplementedError, "#{self.class.name} must implement #connected?"
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
# Placeholder comments for data structures (to be defined elsewhere)
|
128
|
+
# Example:
|
129
|
+
# Project = Struct.new(:id, :key, :name, :adapter_source, keyword_init: true)
|
130
|
+
# Issue = Struct.new(:id, :key, :title, :description, :status, :assignee, :project_id, :adapter_source, keyword_init: true)
|
131
|
+
# Comment = Struct.new(:id, :body, :author, :created_at, :issue_id, :adapter_source, keyword_init: true)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|