clubhouse.io-ruby 0.1.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.
@@ -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