clubhouse.io-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,83 @@
1
+ module Clubhouse
2
+ class BadRequestError < StandardError; end
3
+ class UnauthorizedError < StandardError; end
4
+ class ResourceNotFoundError < StandardError; end
5
+ class UnexpectedError < StandardError; end
6
+ class UnprocessableError < StandardError; end
7
+
8
+ class Client
9
+ include APIActions
10
+
11
+ API_VERSION='v1'.freeze
12
+
13
+ def initialize(token)
14
+ @token = token
15
+ end
16
+
17
+ def basepath
18
+ @basepath ||= "https://api.clubhouse.io/api/#{API_VERSION}"
19
+ end
20
+
21
+ def get(resource)
22
+ req = Net::HTTP::Get.new(build_uri(resource))
23
+ do_request(req)
24
+ end
25
+
26
+ def delete(resource)
27
+ req = Net::HTTP::Delete.new(build_uri(resource))
28
+ do_request(req)
29
+ end
30
+
31
+ def post(resource, body = {})
32
+ req = Net::HTTP::Post.new(build_uri(resource))
33
+ req['Content-Type'] = 'application/json'
34
+ req.body = body.to_json
35
+
36
+ do_request(req)
37
+ end
38
+
39
+ def put(resource, body = {})
40
+ req = Net::HTTP::Put.new(build_uri(resource))
41
+ req['Content-Type'] = 'application/json'
42
+ req.body = body.to_json
43
+
44
+ do_request(req)
45
+ end
46
+
47
+ def raise_error_to_user(response)
48
+ code = response.code.to_i
49
+
50
+ err = case code
51
+ when 400 then BadRequestError
52
+ when 401, 403 then UnauthorizedError
53
+ when 404 then ResourceNotFoundError
54
+ when 422 then UnprocessableError
55
+ else UnexpectedError
56
+ end
57
+
58
+ raise err, response.body
59
+ end
60
+
61
+ private
62
+
63
+ def build_uri(resource)
64
+ uri = URI.parse("#{basepath}/#{resource}")
65
+ uri.query = URI.encode_www_form(token: @token)
66
+ uri
67
+ end
68
+
69
+ def do_request(request)
70
+ http = Net::HTTP.new(request.uri.host, request.uri.port)
71
+ http.use_ssl = true
72
+ response = http.request(request)
73
+
74
+ if [200,201,204].include?(response.code.to_i)
75
+ # Clubhouse API returns content type as application/json for DELETE request with no body :(
76
+ return {} if response.body.nil?
77
+ JSON.parse(response.body)
78
+ else
79
+ raise_error_to_user(response)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,45 @@
1
+ module Clubhouse
2
+ class Comment < BaseResource
3
+ resource :comments
4
+
5
+ attributes :author_id, :created_at, :external_id, :story_id, :text, :updated_at,
6
+ readonly: [:id, :mention_ids, :position]
7
+
8
+ attributes_for_create :author_id, :created_at, :external_id, :text, :updated_at
9
+ attributes_for_update :text
10
+
11
+ def save
12
+ raise MissingStoryIDError, 'story_id is required to create/update comments' unless story_id
13
+ raise ClientNotSetup, "A default client or instance client is not setup" unless client
14
+
15
+ payload = if id
16
+ client.put("stories/#{story_id}/#{self.class.endpoint}/#{id}", update_attributes)
17
+ else
18
+ client.post("stories/#{story_id}/#{self.class.endpoint}", create_attributes)
19
+ end
20
+
21
+ update_object_from_payload(payload)
22
+ end
23
+
24
+ def reload
25
+ payload = client.get("stories/#{story_id}/#{self.class.endpoint}/#{id}")
26
+ update_object_from_payload(payload)
27
+ end
28
+
29
+ class << self
30
+ def find(story_id, comment_id)
31
+ payload = client.get("stories/#{story_id}/#{endpoint}/#{comment_id}")
32
+ new.update_object_from_payload(payload)
33
+ end
34
+
35
+ def delete(story_id, comment_id)
36
+ client.delete("stories/#{story_id}/#{endpoint}/#{comment_id}")
37
+ end
38
+
39
+ def all
40
+ raise NotSupportedByAPIError,
41
+ "You can get all comments associated directly from the story model"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,38 @@
1
+ module Clubhouse
2
+ class Epic < BaseResource
3
+ resource :epics
4
+
5
+ attributes :archived, :created_at, :deadline, :description, :external_id,
6
+ :follower_ids, :name, :owner_ids, :state, :updated_at, readonly: [:id, :comments, :position]
7
+
8
+ attributes_for_create :created_at, :deadline, :description, :external_id,
9
+ :follower_ids, :name, :owner_ids, :state, :updated_at
10
+
11
+ attributes_for_update :after_id, :archived, :before_id, :deadline,
12
+ :description, :follower_ids, :name, :owner_ids, :state
13
+
14
+ def comments
15
+ @_comments ||= Array(@comments).collect{|c| Comment.new.update_object_from_payload(c) }
16
+ end
17
+
18
+ class Comment < BaseResource
19
+ attributes :text, readonly: [:author_id, :created_at, :updated_at, :deleted, :id, :comments]
20
+
21
+ def comments
22
+ @_comments ||= Array(@comments).collect {|c| Comment.new.update_object_from_payload(c) }
23
+ end
24
+
25
+ def save
26
+ raise NotImplemented
27
+ end
28
+
29
+ def self.find(id = nil)
30
+ raise NotImplemented
31
+ end
32
+
33
+ def self.all
34
+ raise NotImplemented
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ class String
2
+ def underscore
3
+ self.gsub(/::/, '/').
4
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
5
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
6
+ tr("-", "_").
7
+ downcase
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module Clubhouse
2
+ class File < BaseResource
3
+ resource :files
4
+
5
+ attributes :created_at, :description, :external_id, :name, :updated_at, :uploader_id,
6
+ readonly: [:content_type, :filename, :id, :mention_ids, :size, :story_ids, :thumbnail_url, :url]
7
+
8
+ attributes_for_update :description, :external_id, :name, :updated_at, :uploader_id
9
+
10
+ def save
11
+ if id.nil?
12
+ raise NotSupportedByAPIError,
13
+ "You can't create a direct file upload, but can create linked files over the API"
14
+ end
15
+
16
+ super
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ module Clubhouse
2
+ class Label < BaseResource
3
+ resource :labels
4
+
5
+ attributes :name, :external_id, readonly: [
6
+ :id,
7
+ :num_stories_in_progress,
8
+ :num_stories_total,
9
+ :num_stories_completed,
10
+ :created_at,
11
+ :updated_at,
12
+ :num_stories_completed
13
+ ]
14
+
15
+ attributes_for_create :name, :external_id
16
+ attributes_for_update :name
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module Clubhouse
2
+ class LinkedFile < BaseResource
3
+ resource 'linked-files'
4
+
5
+ attributes :content_type, :description, :name, :size, :story_id, :thumbnail_url,
6
+ :uploader_id, :url, :type, readonly: [:created_at, :id, :mention_ids, :story_ids, :updated_at]
7
+
8
+ attributes_for_create :content_type, :description, :name, :size, :story_id,
9
+ :thumbnail_url, :type, :uploader_id, :url
10
+
11
+ attributes_for_update :description, :name, :size, :thumbnail_url, :type, :uploader_id, :url
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ module Clubhouse
2
+ class Project < BaseResource
3
+ resource :projects
4
+
5
+ attributes :abbreviation, :archived, :color, :created_at, :description, :external_id,
6
+ :follower_ids, :name, :updated_at, readonly: [:archived, :id, :num_points, :num_stories]
7
+
8
+ attributes_for_create :abbreviation, :color, :created_at, :description,
9
+ :external_id, :follower_ids, :name, :updated_at
10
+
11
+ attributes_for_update :abbreviation, :archived, :color, :description, :follower_ids, :name
12
+
13
+ def stories
14
+ return [] if id.nil?
15
+
16
+ payload = client.get("projects/#{id}/stories")
17
+ Array(payload).collect{|s| Story.new.update_object_from_payload(s) }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,70 @@
1
+ module Clubhouse
2
+ class Story < BaseResource
3
+ resource :stories
4
+
5
+ attributes :archived, :comments, :created_at, :deadline, :description, :epic_id, :estimate,
6
+ :file_ids, :follower_ids, :labels, :linked_file_ids, :name, :owner_ids,
7
+ :position, :project_id, :requested_by_id, :story_links, :story_type, :tasks,
8
+ :updated_at, :workflow_state_id, readonly: :id
9
+
10
+ attributes_for_create :comments, :created_at, :deadline, :description, :epic_id, :estimate,
11
+ :external_id, :file_ids, :labels, :linked_file_ids, :name, :owner_ids,
12
+ :project_id, :requested_by_id, :story_links, :story_type, :tasks,
13
+ :updated_at, :workflow_state_id
14
+
15
+ attributes_for_update :after_id, :archived, :before_id, :deadline, :description, :epic_id,
16
+ :estimate, :file_ids, :follower_ids, :labels, :linked_file_ids, :name,
17
+ :owner_ids, :project_id, :requested_by_id, :story_type, :workflow_state_id
18
+
19
+
20
+ def comments
21
+ @_comments ||= Array(@comments).collect {|c| Comment.new.update_object_from_payload(c) }
22
+ end
23
+
24
+ def tasks
25
+ @_tasks ||= Array(@tasks).collect {|t| Task.new.update_object_from_payload(t) }
26
+ end
27
+
28
+ def add_comment(text)
29
+ add_resource(:comment) do
30
+ comment = Comment.new(text: text, story_id: id)
31
+ comment.save
32
+ comments << comment
33
+ end
34
+ end
35
+
36
+ def add_task(desc)
37
+ add_resource(:task) do
38
+ task = Task.new(description: desc, story_id: id)
39
+ task.save
40
+ tasks << task
41
+ end
42
+ end
43
+
44
+ def update_object_from_payload(attr = {})
45
+ super
46
+ instance_variable_set("@_comments", nil)
47
+ instance_variable_set("@_tasks", nil)
48
+ self
49
+ end
50
+
51
+ class << self
52
+ def all
53
+ raise NotSupportedByAPIError,
54
+ 'Use Story.search(..) to return stories matching your search query'
55
+ end
56
+
57
+ def search(attr = {})
58
+ payload = client.post("#{endpoint}/search", attr)
59
+ payload.collect {|s| new.update_object_from_payload(s) }
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def add_resource(type)
66
+ raise StoryNotSavedError, "Please save the story to use the add #{type} method" unless id
67
+ yield
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,17 @@
1
+ module Clubhouse
2
+ class StoryLink < BaseResource
3
+ resource 'story-links'
4
+
5
+ attributes :object_id, :subject_id, :verb, readonly: [ :created_at, :id, :updated_at ]
6
+ attributes_for_create :object_id, :subject_id, :verb
7
+
8
+ def save
9
+ raise NotSupportedByAPIError, "You can't update a story link" if id
10
+ super
11
+ end
12
+
13
+ def self.all
14
+ raise NotSupportedByAPIError, "Not supported by the API"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ module Clubhouse
2
+ class Task < BaseResource
3
+ resource :tasks
4
+
5
+ attributes :complete, :created_at, :description, :external_id, :owner_ids,
6
+ :updated_at, :story_id, readonly: [:completed_at, :id, :mention_ids, :position]
7
+
8
+ attributes_for_create :complete, :created_at, :description, :external_id, :owner_ids, :updated_at
9
+ attributes_for_update :after_id, :before_id, :complete, :description, :owner_ids
10
+
11
+
12
+ def save
13
+ raise MissingStoryIDError, 'story_id is required to create/update tasks' unless story_id
14
+ raise ClientNotSetup, "A default client or instance client is not setup" unless client
15
+
16
+ payload = if id
17
+ client.put("stories/#{story_id}/#{self.class.endpoint}/#{id}", update_attributes)
18
+ else
19
+ client.post("stories/#{story_id}/#{self.class.endpoint}", create_attributes)
20
+ end
21
+
22
+ update_object_from_payload(payload)
23
+ end
24
+
25
+ def reload
26
+ payload = client.get("stories/#{story_id}/#{self.class.endpoint}/#{id}")
27
+ update_object_from_payload(payload)
28
+ end
29
+
30
+ class << self
31
+ def find(story_id, task_id)
32
+ payload = client.get("stories/#{story_id}/#{endpoint}/#{task_id}")
33
+ new.update_object_from_payload(payload)
34
+ end
35
+
36
+ def delete(story_id, task_id)
37
+ client.delete("stories/#{story_id}/tasks/#{task_id}")
38
+ end
39
+
40
+ def all
41
+ raise NotSupportedByAPIError, "You can get all tasks associated directly from the story model"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,34 @@
1
+ module Clubhouse
2
+ class User < BaseResource
3
+ resource :users
4
+
5
+ attributes :name, readonly: [ :deactivated, :id, :name, :permissions,
6
+ :two_factor_auth_activated, :username]
7
+
8
+ Permission = Struct.new(:created_at, :disabled, :email_address,
9
+ :gravatar_hash, :id, :initials, :role, :updated_at)
10
+
11
+ def save
12
+ raise NotSupportedByAPIError, "You can't create users over the API, please use clubhouse web"
13
+ end
14
+
15
+ def self.delete(id = nil)
16
+ raise NotSupportedByAPIError, "You can't delete users over the API, please use clubhouse web"
17
+ end
18
+
19
+ def permissions
20
+ @_permissions ||= Array(@permissions).collect do |p|
21
+ Permission.new(
22
+ p['created_at'],
23
+ p['disabled'],
24
+ p['email_address'],
25
+ p['gravatar_hash'],
26
+ p['id'],
27
+ p['initials'],
28
+ p['role'],
29
+ p['updated_at']
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module Clubhouse
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,42 @@
1
+ module Clubhouse
2
+ class Workflow < BaseResource
3
+ resource :workflows
4
+
5
+ attributes :default_state_id, readonly: [:id, :created_at, :states, :updated_at]
6
+
7
+ State = Struct.new(:color, :created_at, :description, :id, :name,
8
+ :num_stories, :position, :type, :updated_at, :verb)
9
+
10
+ def save
11
+ raise NotSupportedByAPIError,
12
+ "You can't manage workflows over the API, please use clubhouse web"
13
+ end
14
+
15
+ def self.delete(id = nil)
16
+ raise NotSupportedByAPIError,
17
+ "You can't delete workflows over the API, please use clubhouse web"
18
+ end
19
+
20
+ def self.find(id = nil)
21
+ raise NotSupportedByAPIError,
22
+ "You can only list all workflows, please use Clubhouse::Workflow.all"
23
+ end
24
+
25
+ def states
26
+ @_states ||= Array(@states).collect do |s|
27
+ State.new(
28
+ s['color'],
29
+ s['created_at'],
30
+ s['description'],
31
+ s['id'],
32
+ s['name'],
33
+ s['num_stories'],
34
+ s['position'],
35
+ s['type'],
36
+ s['updated_at'],
37
+ s['verb']
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end