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
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
# Handles configuration for the ActiveProject gem, including adapter settings.
|
5
|
+
class Configuration
|
6
|
+
attr_reader :adapter_configs
|
7
|
+
attr_accessor :user_agent
|
8
|
+
|
9
|
+
# Maps adapter names (symbols) to their specific configuration classes.
|
10
|
+
# Add other adapters here when they need specific config classes.
|
11
|
+
ADAPTER_CONFIG_CLASSES = {
|
12
|
+
trello: Configurations::TrelloConfiguration
|
13
|
+
# :jira => Configurations::JiraConfiguration,
|
14
|
+
# :basecamp => Configurations::BasecampConfiguration,
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@adapter_configs = {}
|
19
|
+
end
|
20
|
+
@user_agent = "ActiveProject Gem (github.com/seuros/activeproject)"
|
21
|
+
|
22
|
+
# Adds or updates the configuration for a specific adapter.
|
23
|
+
# If a block is given and a specific configuration class exists for the adapter,
|
24
|
+
# an instance of that class is yielded to the block. Otherwise, a basic
|
25
|
+
# configuration object is created from the options hash.
|
26
|
+
#
|
27
|
+
# @param name [Symbol] The name of the adapter (e.g., :jira, :trello).
|
28
|
+
# @param options [Hash] Configuration options for the adapter (e.g., site, api_key, token).
|
29
|
+
# @yield [BaseAdapterConfiguration] Yields an adapter-specific configuration object if a block is given.
|
30
|
+
def add_adapter(name, options = {}, &block)
|
31
|
+
unless name.is_a?(Symbol)
|
32
|
+
raise ArgumentError, "Adapter name must be a Symbol (e.g., :jira)"
|
33
|
+
end
|
34
|
+
|
35
|
+
config_class = ADAPTER_CONFIG_CLASSES[name]
|
36
|
+
|
37
|
+
# Use specific config class if block is given and class exists
|
38
|
+
if block && config_class
|
39
|
+
adapter_config_obj = config_class.new(options)
|
40
|
+
yield adapter_config_obj # Allow block to modify the specific config object
|
41
|
+
@adapter_configs[name] = adapter_config_obj.freeze
|
42
|
+
# Use specific config class if no block but class exists (handles options like status_mappings passed directly)
|
43
|
+
elsif config_class
|
44
|
+
adapter_config_obj = config_class.new(options)
|
45
|
+
@adapter_configs[name] = adapter_config_obj.freeze
|
46
|
+
# Fallback to base config class if no specific class or no block
|
47
|
+
else
|
48
|
+
@adapter_configs[name] = Configurations::BaseAdapterConfiguration.new(options).freeze
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Retrieves the configuration object for a specific adapter.
|
53
|
+
# @param name [Symbol] The name of the adapter.
|
54
|
+
# @return [BaseAdapterConfiguration, nil] The configuration object or nil if not found.
|
55
|
+
def adapter_config(name)
|
56
|
+
@adapter_configs[name]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
module Configurations
|
5
|
+
# Base class for adapter configurations, holding common options.
|
6
|
+
class BaseAdapterConfiguration
|
7
|
+
attr_reader :options
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@options = options.dup # Duplicate to allow modification before freezing
|
11
|
+
end
|
12
|
+
|
13
|
+
# Allow accessing options via method calls
|
14
|
+
def method_missing(method_name, *arguments, &block)
|
15
|
+
if options.key?(method_name) && arguments.empty? && !block
|
16
|
+
options[method_name]
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def respond_to_missing?(method_name, include_private = false)
|
23
|
+
options.key?(method_name) || super
|
24
|
+
end
|
25
|
+
|
26
|
+
def freeze
|
27
|
+
@options.freeze
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
module Configurations
|
5
|
+
# Holds Trello-specific configuration options.
|
6
|
+
class TrelloConfiguration < BaseAdapterConfiguration
|
7
|
+
# @!attribute [rw] status_mappings
|
8
|
+
# @return [Hash] Mappings from Board IDs to List ID/Name to Status Symbol.
|
9
|
+
# @example
|
10
|
+
# {
|
11
|
+
# 'board_id_1' => { 'list_id_open' => :open, 'list_id_closed' => :closed },
|
12
|
+
# 'board_id_2' => { 'Done List Name' => :closed } # Example using names (less reliable)
|
13
|
+
# }
|
14
|
+
attr_accessor :status_mappings
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
super
|
18
|
+
@status_mappings = options.delete(:status_mappings) || {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def freeze
|
22
|
+
# Ensure nested hashes are also frozen
|
23
|
+
@status_mappings.each do |board_id, mappings|
|
24
|
+
mappings.freeze
|
25
|
+
end
|
26
|
+
@status_mappings.freeze
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
# Base class for all ActiveProject errors
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
# Raised when authentication with the external API fails
|
8
|
+
class AuthenticationError < Error; end
|
9
|
+
|
10
|
+
# Raised when a requested resource is not found
|
11
|
+
class NotFoundError < Error; end
|
12
|
+
|
13
|
+
# Raised when the external API rate limit is exceeded
|
14
|
+
class RateLimitError < Error; end
|
15
|
+
|
16
|
+
# Raised for general API errors (e.g., 5xx status codes)
|
17
|
+
class ApiError < Error
|
18
|
+
attr_reader :original_error, :status_code, :response_body
|
19
|
+
|
20
|
+
def initialize(message = nil, original_error: nil, status_code: nil, response_body: nil)
|
21
|
+
super(message)
|
22
|
+
@original_error = original_error
|
23
|
+
@status_code = status_code
|
24
|
+
@response_body = response_body
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Raised for validation errors (e.g., 400/422 status codes with field details)
|
29
|
+
class ValidationError < ApiError
|
30
|
+
attr_reader :errors
|
31
|
+
|
32
|
+
def initialize(message = nil, errors: {}, original_error: nil, status_code: nil, response_body: nil)
|
33
|
+
super(message, original_error: original_error, status_code: status_code, response_body: response_body)
|
34
|
+
@errors = errors # Expects a hash like { field: ['message1', 'message2'] }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Raised when an adapter method is not implemented
|
39
|
+
class NotImplementedError < Error; end
|
40
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
# Factory class for creating and finding resource objects (Project, Issue, etc.)
|
5
|
+
# associated with a specific adapter.
|
6
|
+
class ResourceFactory
|
7
|
+
# @param adapter [Adapters::Base] The adapter instance.
|
8
|
+
# @param resource_class [Class] The resource class (e.g., Resources::Project).
|
9
|
+
def initialize(adapter:, resource_class:)
|
10
|
+
@adapter = adapter
|
11
|
+
@resource_class = resource_class
|
12
|
+
end
|
13
|
+
|
14
|
+
# Fetches all resources of the associated type.
|
15
|
+
# Delegates to the appropriate adapter list method.
|
16
|
+
# @param args [Array] Arguments to pass to the adapter's list method (e.g., project_id for issues).
|
17
|
+
# @return [Array<BaseResource>] An array of resource objects.
|
18
|
+
def all(*args)
|
19
|
+
list_method = determine_list_method
|
20
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
21
|
+
primary_arg = args.first
|
22
|
+
|
23
|
+
# Call adapter method with appropriate arguments
|
24
|
+
if primary_arg
|
25
|
+
@adapter.send(list_method, primary_arg, options)
|
26
|
+
else
|
27
|
+
# Handle case where list method might not take options (like list_projects)
|
28
|
+
if @adapter.method(list_method).arity == 0
|
29
|
+
@adapter.send(list_method)
|
30
|
+
else
|
31
|
+
@adapter.send(list_method, options)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Finds a specific resource by its ID.
|
37
|
+
# Delegates to the appropriate adapter find method.
|
38
|
+
# @param id [String, Integer] The ID or key of the resource.
|
39
|
+
# @param context [Hash] Optional context needed by some find methods (e.g., :project_id for Basecamp issues).
|
40
|
+
# @return [BaseResource, nil] The found resource object or nil.
|
41
|
+
def find(id, context = {})
|
42
|
+
find_method = determine_find_method
|
43
|
+
|
44
|
+
# Pass context only if provided and the find method accepts it
|
45
|
+
if !context.empty?
|
46
|
+
@adapter.send(find_method, id, context)
|
47
|
+
else
|
48
|
+
@adapter.send(find_method, id)
|
49
|
+
end
|
50
|
+
rescue ActiveProject::NotFoundError
|
51
|
+
nil # Return nil if the resource is not found by the adapter
|
52
|
+
end
|
53
|
+
|
54
|
+
# Fetches the first resource of the associated type.
|
55
|
+
# @param args [Array] Arguments to pass to the adapter's list method.
|
56
|
+
# @return [BaseResource, nil] The first resource object found or nil.
|
57
|
+
def first(*args)
|
58
|
+
all(*args).first
|
59
|
+
end
|
60
|
+
|
61
|
+
# Filters resources based on given conditions (client-side).
|
62
|
+
# @param conditions [Hash] Conditions to filter by.
|
63
|
+
# @param list_args [Array] Arguments for the underlying #all call.
|
64
|
+
# @return [Array<BaseResource>] Matching resources.
|
65
|
+
def where(conditions, *list_args)
|
66
|
+
resources = all(*list_args)
|
67
|
+
resources.select do |resource|
|
68
|
+
conditions.all? do |key, value|
|
69
|
+
resource.respond_to?(key) && resource.send(key) == value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Builds a new, unsaved resource instance.
|
75
|
+
# @param attributes [Hash] Attributes for the new resource.
|
76
|
+
# @return [BaseResource] A new instance of the resource class.
|
77
|
+
def build(attributes = {})
|
78
|
+
merged_attrs = attributes.merge(
|
79
|
+
adapter_source: @adapter.class.name.split("::").last.sub("Adapter", "").downcase.to_sym,
|
80
|
+
raw_data: attributes
|
81
|
+
)
|
82
|
+
@resource_class.new(@adapter, merged_attrs)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Builds and saves a new resource instance.
|
86
|
+
# @param attributes [Hash] Attributes for the new resource.
|
87
|
+
# @return [BaseResource] The created resource object.
|
88
|
+
# @raise [NotImplementedError] Currently raises because #save is not implemented.
|
89
|
+
def create(attributes = {})
|
90
|
+
# Determine the correct adapter create method based on resource type
|
91
|
+
create_method = determine_create_method
|
92
|
+
# Note: Assumes create methods on adapters take attributes hash directly
|
93
|
+
# Context like project_id needs to be part of the attributes hash if required by adapter
|
94
|
+
@adapter.send(create_method, attributes)
|
95
|
+
# A full implementation would likely involve build then save:
|
96
|
+
# resource = build(attributes)
|
97
|
+
# resource.save
|
98
|
+
# resource
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def determine_list_method
|
104
|
+
method_name = case @resource_class.name
|
105
|
+
when "ActiveProject::Resources::Project" then :list_projects
|
106
|
+
when "ActiveProject::Resources::Issue" then :list_issues
|
107
|
+
else raise "Cannot determine list method for #{@resource_class.name}"
|
108
|
+
end
|
109
|
+
raise NotImplementedError, "#{@adapter.class.name} does not implement ##{method_name}" unless @adapter.respond_to?(method_name)
|
110
|
+
method_name
|
111
|
+
end
|
112
|
+
|
113
|
+
def determine_find_method
|
114
|
+
method_name = case @resource_class.name
|
115
|
+
when "ActiveProject::Resources::Project" then :find_project
|
116
|
+
when "ActiveProject::Resources::Issue" then :find_issue
|
117
|
+
else raise "Cannot determine find method for #{@resource_class.name}"
|
118
|
+
end
|
119
|
+
raise NotImplementedError, "#{@adapter.class.name} does not implement ##{method_name}" unless @adapter.respond_to?(method_name)
|
120
|
+
method_name
|
121
|
+
end
|
122
|
+
|
123
|
+
def determine_create_method
|
124
|
+
singular_name = @resource_class.name.split("::").last.downcase.to_sym
|
125
|
+
method_name = :"create_#{singular_name}"
|
126
|
+
raise NotImplementedError, "#{@adapter.class.name} does not implement ##{method_name}" unless @adapter.respond_to?(method_name)
|
127
|
+
method_name
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
module Resources
|
5
|
+
# Base class for resource objects (Project, Issue, etc.)
|
6
|
+
# Provides common initialization and attribute access via method_missing.
|
7
|
+
class BaseResource
|
8
|
+
attr_reader :adapter, :raw_data, :attributes
|
9
|
+
|
10
|
+
# @param adapter [Adapters::Base] The adapter instance that fetched/created this resource.
|
11
|
+
# @param attributes [Hash] A hash of attributes for the resource.
|
12
|
+
def initialize(adapter, attributes = {})
|
13
|
+
@adapter = adapter
|
14
|
+
# Store raw_data if provided, otherwise duplicate the input attributes
|
15
|
+
@raw_data = attributes.key?(:raw_data) ? attributes[:raw_data] : attributes.dup
|
16
|
+
# Store attributes for method_missing access, remove raw_data key if it exists
|
17
|
+
@attributes = attributes.dup
|
18
|
+
@attributes.delete(:raw_data)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Basic inspection using defined members if available, otherwise attributes.
|
22
|
+
def inspect
|
23
|
+
members_to_show = self.class.members.empty? ? @attributes.keys : self.class.members
|
24
|
+
attrs_str = members_to_show.map { |m| "#{m}=#{send(m).inspect}" }.join(", ")
|
25
|
+
"#<#{self.class.name} #{attrs_str}>"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Method missing for accessing attributes stored in @attributes hash.
|
29
|
+
def method_missing(method_name, *arguments, &block)
|
30
|
+
if @attributes.key?(method_name)
|
31
|
+
# Return attribute value if no arguments are given (getter)
|
32
|
+
arguments.empty? ? @attributes[method_name] : super
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Ensure respond_to? works correctly with method_missing.
|
39
|
+
def respond_to_missing?(method_name, include_private = false)
|
40
|
+
@attributes.key?(method_name) || super
|
41
|
+
end
|
42
|
+
|
43
|
+
# Class method to define expected members (mainly for introspection/documentation).
|
44
|
+
def self.members
|
45
|
+
@members ||= []
|
46
|
+
end
|
47
|
+
|
48
|
+
# Defines expected members for the resource class.
|
49
|
+
def self.def_members(*args)
|
50
|
+
@members ||= []
|
51
|
+
@members.concat(args.map(&:to_sym))
|
52
|
+
# No explicit attr_reader needed when using method_missing
|
53
|
+
end
|
54
|
+
|
55
|
+
# Placeholder methods for ORM-like behavior
|
56
|
+
def save
|
57
|
+
raise NotImplementedError, "#save not yet implemented for #{self.class.name}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def update(attributes)
|
61
|
+
raise NotImplementedError, "#update not yet implemented for #{self.class.name}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def delete
|
65
|
+
raise NotImplementedError, "#delete not yet implemented for #{self.class.name}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_resource"
|
4
|
+
|
5
|
+
module ActiveProject
|
6
|
+
module Resources
|
7
|
+
# Represents a Comment on an Issue
|
8
|
+
class Comment < BaseResource
|
9
|
+
def_members :id, :body, :author, :created_at, :updated_at, :issue_id,
|
10
|
+
:adapter_source
|
11
|
+
# raw_data and adapter are inherited from BaseResource
|
12
|
+
|
13
|
+
# Add comment-specific methods here later (e.g., save, update, delete)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
module Resources
|
5
|
+
# Represents an Issue (e.g., Jira Issue, Trello Card, Basecamp Todo)
|
6
|
+
class Issue < BaseResource
|
7
|
+
def_members :id, :key, :title, :description, :status, :assignees,
|
8
|
+
:reporter, :project_id, :created_at, :updated_at, :due_on,
|
9
|
+
:priority, :adapter_source
|
10
|
+
# raw_data and adapter are inherited from BaseResource
|
11
|
+
|
12
|
+
|
13
|
+
# Saves the issue (creates if new, updates if existing).
|
14
|
+
# Placeholder - Full implementation requires attribute tracking and adapter delegation.
|
15
|
+
# @return [Boolean] true if save was successful, false otherwise.
|
16
|
+
def save
|
17
|
+
raise NotImplementedError, "#save not yet implemented for #{self.class.name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Updates the issue with the given attributes and saves it.
|
21
|
+
# Placeholder - Full implementation requires attribute tracking and adapter delegation.
|
22
|
+
# @param attributes [Hash] Attributes to update.
|
23
|
+
# @return [Boolean] true if update was successful, false otherwise.
|
24
|
+
def update(attributes)
|
25
|
+
# Basic implementation could be:
|
26
|
+
# attributes.each { |k, v| instance_variable_set("@#{k}", v) if respond_to?(k) } # Need setters or direct ivar access
|
27
|
+
# save
|
28
|
+
raise NotImplementedError, "#update not yet implemented for #{self.class.name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# Returns an association proxy for accessing comments on this issue.
|
33
|
+
# @return [AssociationProxy<Resources::Comment>]
|
34
|
+
def comments
|
35
|
+
AssociationProxy.new(owner: self, adapter: @adapter, association_name: :comments)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Add issue-specific methods here later (e.g., comments association, save, update)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
module Resources
|
5
|
+
# Represents a Project (e.g., Jira Project, Trello Board, Basecamp Project)
|
6
|
+
class Project < BaseResource
|
7
|
+
def_members :id, :key, :name, :adapter_source
|
8
|
+
# raw_data and adapter are inherited from BaseResource
|
9
|
+
|
10
|
+
|
11
|
+
# Returns an association proxy for accessing issues within this project.
|
12
|
+
# @return [AssociationProxy<Resources::Issue>]
|
13
|
+
def issues
|
14
|
+
AssociationProxy.new(owner: self, adapter: @adapter, association_name: :issues)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add project-specific methods here later (e.g., issues association)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_resource"
|
4
|
+
|
5
|
+
module ActiveProject
|
6
|
+
module Resources
|
7
|
+
# Represents a User
|
8
|
+
class User < BaseResource
|
9
|
+
def_members :id, :name, :email, :adapter_source
|
10
|
+
# raw_data and adapter are inherited from BaseResource
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveProject
|
4
|
+
# Represents a standardized event parsed from a webhook payload.
|
5
|
+
# Using Struct for simplicity for now. Could be a full class inheriting BaseResource if needed.
|
6
|
+
WebhookEvent = Struct.new(
|
7
|
+
:event_type, # Symbol representing the event (e.g., :issue_created, :comment_added)
|
8
|
+
:object_kind, # Symbol representing the type of object involved (e.g., :issue, :comment)
|
9
|
+
:event_object_id, # String or Integer ID of the primary object (renamed from object_id)
|
10
|
+
:object_key, # String key/slug of the primary object (if applicable, e.g., Jira issue key)
|
11
|
+
:project_id, # String or Integer ID of the associated project/board/bucket
|
12
|
+
:actor, # User resource or Hash representing the user who triggered the event
|
13
|
+
:timestamp, # Time object representing when the event occurred
|
14
|
+
:adapter_source, # Symbol identifying the source adapter (e.g., :jira, :trello, :basecamp)
|
15
|
+
:changes, # Hash detailing specific changes (if applicable, e.g., for updates)
|
16
|
+
:object_data, # Optional: Hash containing more detailed data about the object
|
17
|
+
:raw_data, # The original, parsed webhook payload hash
|
18
|
+
keyword_init: true
|
19
|
+
)
|
20
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "zeitwerk"
|
2
|
+
require_relative "active_project/errors"
|
3
|
+
require_relative "active_project/version"
|
4
|
+
|
5
|
+
module ActiveProject
|
6
|
+
class << self
|
7
|
+
attr_writer :configuration
|
8
|
+
|
9
|
+
def configuration
|
10
|
+
@configuration ||= Configuration.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def configure
|
14
|
+
yield(configuration)
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
# Returns the configured User-Agent string, including the gem version.
|
19
|
+
# @return [String] The User-Agent string.
|
20
|
+
def user_agent
|
21
|
+
base_agent = configuration.user_agent || "ActiveProject Gem (github.com/seuros/activeproject)"
|
22
|
+
"#{base_agent} v#{ActiveProject::VERSION}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns a memoized instance of the requested adapter.
|
26
|
+
# @param adapter_name [Symbol] The name of the adapter (e.g., :jira, :trello).
|
27
|
+
# @return [Adapters::Base] An instance of the requested adapter.
|
28
|
+
# @raise [ArgumentError] if the adapter configuration is missing or invalid.
|
29
|
+
# @raise [LoadError] if the adapter class cannot be found.
|
30
|
+
def adapter(adapter_name)
|
31
|
+
@adapters ||= {}
|
32
|
+
@adapters[adapter_name] ||= begin
|
33
|
+
config = configuration.adapter_config(adapter_name)
|
34
|
+
|
35
|
+
unless config.is_a?(ActiveProject::Configurations::BaseAdapterConfiguration)
|
36
|
+
raise ArgumentError, "Configuration for adapter ':#{adapter_name}' not found or invalid. Use ActiveProject.configure."
|
37
|
+
end
|
38
|
+
|
39
|
+
# Use string-based constant lookup with the full namespace path
|
40
|
+
adapter_class_name = "ActiveProject::Adapters::#{adapter_name.to_s.capitalize}Adapter"
|
41
|
+
|
42
|
+
# Ensure the adapter class is loaded
|
43
|
+
require "active_project/adapters/#{adapter_name}_adapter"
|
44
|
+
|
45
|
+
# Get the constant with the full path
|
46
|
+
adapter_class = Object.const_get(adapter_class_name)
|
47
|
+
|
48
|
+
adapter_class.new(config: config)
|
49
|
+
rescue LoadError, NameError => e
|
50
|
+
raise LoadError, "Could not find adapter class #{adapter_class_name}: #{e.message}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
57
|
+
loader.inflector.inflect("activeproject" => "ActiveProject")
|
58
|
+
loader.do_not_eager_load("#{__dir__}/active_project/adapters")
|
59
|
+
loader.ignore("#{__dir__}/active_project/errors.rb")
|
60
|
+
loader.ignore("#{__dir__}/active_project/version.rb")
|
61
|
+
loader.setup
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activeproject
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Abdelkader Boudih
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-04-07 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: activesupport
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '8.0'
|
19
|
+
- - "<"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '9.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '8.0'
|
29
|
+
- - "<"
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '9.0'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: faraday
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: faraday-retry
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
type: :runtime
|
54
|
+
prerelease: false
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: mocha
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
type: :development
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
description: Provides a unified API client for interacting with various project management
|
75
|
+
platforms like Jira, Basecamp, and Trello. Aims to normalize core models (projects,
|
76
|
+
tasks, comments) and workflows for easier integration in Rails applications.
|
77
|
+
email:
|
78
|
+
- terminale@gmail.com
|
79
|
+
executables: []
|
80
|
+
extensions: []
|
81
|
+
extra_rdoc_files: []
|
82
|
+
files:
|
83
|
+
- MIT-LICENSE
|
84
|
+
- README.md
|
85
|
+
- Rakefile
|
86
|
+
- lib/active_project/adapters/base.rb
|
87
|
+
- lib/active_project/adapters/basecamp_adapter.rb
|
88
|
+
- lib/active_project/adapters/jira_adapter.rb
|
89
|
+
- lib/active_project/adapters/trello_adapter.rb
|
90
|
+
- lib/active_project/association_proxy.rb
|
91
|
+
- lib/active_project/configuration.rb
|
92
|
+
- lib/active_project/configurations/base_adapter_configuration.rb
|
93
|
+
- lib/active_project/configurations/trello_configuration.rb
|
94
|
+
- lib/active_project/errors.rb
|
95
|
+
- lib/active_project/resource_factory.rb
|
96
|
+
- lib/active_project/resources/base_resource.rb
|
97
|
+
- lib/active_project/resources/comment.rb
|
98
|
+
- lib/active_project/resources/issue.rb
|
99
|
+
- lib/active_project/resources/project.rb
|
100
|
+
- lib/active_project/resources/user.rb
|
101
|
+
- lib/active_project/version.rb
|
102
|
+
- lib/active_project/webhook_event.rb
|
103
|
+
- lib/activeproject.rb
|
104
|
+
homepage: https://github.com/seuros/activeproject
|
105
|
+
licenses:
|
106
|
+
- MIT
|
107
|
+
metadata:
|
108
|
+
homepage_uri: https://github.com/seuros/activeproject
|
109
|
+
source_code_uri: https://github.com/seuros/activeproject
|
110
|
+
rdoc_options: []
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
requirements: []
|
124
|
+
rubygems_version: 3.6.2
|
125
|
+
specification_version: 4
|
126
|
+
summary: A standardized Ruby interface for multiple project management APIs (Jira,
|
127
|
+
Basecamp, Trello, etc.).
|
128
|
+
test_files: []
|