panoptes-client 0.2.7 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8dc546adb36b049e8da428336c52673a0d9f983d
4
- data.tar.gz: 85649db5d4650c8d9f92dffd5a23ab85ff8db1f4
3
+ metadata.gz: 3621f283592595fbf4099b4bfc445e5114b0e18e
4
+ data.tar.gz: 3a54d9787e2b0db82594ccd408376696161a780c
5
5
  SHA512:
6
- metadata.gz: 60f2d4972099ce2d3273a4b4da5cfe1941b6d0f62c86d7fa390cf8db304c7a23971886289f1fea5e9d0d0e93ab8723d2b7cd045dda2e652b5ea9c544bfa98663
7
- data.tar.gz: ade3768a597807f6fff9fd9636ff892f6c21f86b45c792d83cf6d8310d0038a253b32a3fa09477490825e7ce89734d0495a1994849311429cadf5c88e490229b
6
+ metadata.gz: 516d1f3d47837241e73c0a30cc21ea3691c367b290a39284f088d98873d1c4aa8b2c63ba8b97ce2325dfc8befbedd170d4e473ca3497e2ca8cc0ec2dc8eeae43
7
+ data.tar.gz: 5d3f8289862edb747d826bce689ad3f3eff82ab310c7e7ff29d13998bfc807671982ae68baf3f8658cf0a5b793614da7c8b98cb45cc791d941556a4462c5b194
data/.hound.yml ADDED
@@ -0,0 +1,2 @@
1
+ ruby:
2
+ config_file: .ruby-style.yml
data/.ruby-style.yml ADDED
@@ -0,0 +1,206 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ Exclude:
5
+ - "vendor/**/*"
6
+ - "db/schema.rb"
7
+ UseCache: false
8
+
9
+ Style/DotPosition:
10
+ Description: Checks the position of the dot in multi-line method calls.
11
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains
12
+ Enabled: true
13
+ EnforcedStyle: leading
14
+ SupportedStyles:
15
+ - leading
16
+ - trailing
17
+ Style/FileName:
18
+ Description: Use snake_case for source file names.
19
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#snake-case-files
20
+ Enabled: true
21
+ Exclude: []
22
+ Style/GuardClause:
23
+ Description: Check for conditionals that can be replaced with guard clauses
24
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals
25
+ Enabled: false
26
+ MinBodyLength: 1
27
+ Style/IfUnlessModifier:
28
+ Description: Favor modifier if/unless usage when you have a single-line body.
29
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier
30
+ Enabled: false
31
+ MaxLineLength: 80
32
+ Style/OptionHash:
33
+ Description: Don't use option hashes when you can use keyword arguments.
34
+ Enabled: false
35
+ Style/ParallelAssignment:
36
+ Enabled: false
37
+ Style/PercentLiteralDelimiters:
38
+ Description: Use `%`-literal delimiters consistently
39
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-literal-braces
40
+ Enabled: false
41
+ PreferredDelimiters:
42
+ "%": "()"
43
+ "%i": "()"
44
+ "%q": "()"
45
+ "%Q": "()"
46
+ "%r": "{}"
47
+ "%s": "()"
48
+ "%w": "()"
49
+ "%W": "()"
50
+ "%x": "()"
51
+ Style/PredicateName:
52
+ Description: Check the names of predicate methods.
53
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark
54
+ Enabled: true
55
+ NamePrefix:
56
+ - is_
57
+ - has_
58
+ - have_
59
+ NamePrefixBlacklist:
60
+ - is_
61
+ Exclude:
62
+ - spec/**/*
63
+ Style/RaiseArgs:
64
+ Description: Checks the arguments passed to raise/fail.
65
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#exception-class-messages
66
+ Enabled: false
67
+ EnforcedStyle: exploded
68
+ SupportedStyles:
69
+ - compact
70
+ - exploded
71
+ Style/SignalException:
72
+ Description: Checks for proper usage of fail and raise.
73
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#fail-method
74
+ Enabled: false
75
+ EnforcedStyle: semantic
76
+ SupportedStyles:
77
+ - only_raise
78
+ - only_fail
79
+ - semantic
80
+ Style/SingleLineBlockParams:
81
+ Description: Enforces the names of some block params.
82
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#reduce-blocks
83
+ Enabled: false
84
+ Methods:
85
+ - reduce:
86
+ - a
87
+ - e
88
+ - inject:
89
+ - a
90
+ - e
91
+ Style/SingleLineMethods:
92
+ Description: Avoid single-line methods.
93
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-single-line-methods
94
+ Enabled: false
95
+ AllowIfMethodIsEmpty: true
96
+ Metrics/AbcSize:
97
+ Description: A calculated magnitude based on number of assignments, branches, and
98
+ conditions.
99
+ Enabled: false
100
+ Max: 15
101
+ Metrics/ClassLength:
102
+ Description: Avoid classes longer than 100 lines of code.
103
+ Enabled: false
104
+ CountComments: false
105
+ Max: 100
106
+ Metrics/LineLength:
107
+ Enabled: false
108
+ Metrics/ModuleLength:
109
+ CountComments: false
110
+ Max: 100
111
+ Description: Avoid modules longer than 100 lines of code.
112
+ Enabled: false
113
+ Metrics/CyclomaticComplexity:
114
+ Description: A complexity metric that is strongly correlated to the number of test
115
+ cases needed to validate a method.
116
+ Enabled: false
117
+ Max: 6
118
+ Metrics/MethodLength:
119
+ Description: Avoid methods longer than 10 lines of code.
120
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#short-methods
121
+ Enabled: false
122
+ CountComments: false
123
+ Max: 10
124
+ Metrics/ParameterLists:
125
+ Description: Avoid parameter lists longer than three or four parameters.
126
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#too-many-params
127
+ Enabled: false
128
+ Max: 5
129
+ CountKeywordArgs: true
130
+ Metrics/PerceivedComplexity:
131
+ Description: A complexity metric geared towards measuring complexity for a human
132
+ reader.
133
+ Enabled: false
134
+ Max: 7
135
+ Lint/AssignmentInCondition:
136
+ Description: Don't use assignment in conditions.
137
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition
138
+ Enabled: false
139
+ AllowSafeAssignment: true
140
+ Style/InlineComment:
141
+ Description: Avoid inline comments.
142
+ Enabled: false
143
+ Style/AccessorMethodName:
144
+ Description: Check the naming of accessor methods for get_/set_.
145
+ Enabled: false
146
+ Style/Alias:
147
+ Description: Use alias_method instead of alias.
148
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#alias-method
149
+ Enabled: false
150
+ Style/Documentation:
151
+ Description: Document classes and non-namespace modules.
152
+ Enabled: false
153
+ Style/DoubleNegation:
154
+ Description: Checks for uses of double negation (!!).
155
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-bang-bang
156
+ Enabled: false
157
+ Style/EachWithObject:
158
+ Description: Prefer `each_with_object` over `inject` or `reduce`.
159
+ Enabled: false
160
+ Style/EmptyLiteral:
161
+ Description: Prefer literals to Array.new/Hash.new/String.new.
162
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#literal-array-hash
163
+ Enabled: false
164
+ Style/ModuleFunction:
165
+ Description: Checks for usage of `extend self` in modules.
166
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#module-function
167
+ Enabled: false
168
+ Style/OneLineConditional:
169
+ Description: Favor the ternary operator(?:) over if/then/else/end constructs.
170
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#ternary-operator
171
+ Enabled: false
172
+ Style/PerlBackrefs:
173
+ Description: Avoid Perl-style regex back references.
174
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers
175
+ Enabled: false
176
+ Style/Send:
177
+ Description: Prefer `Object#__send__` or `Object#public_send` to `send`, as `send`
178
+ may overlap with existing methods.
179
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#prefer-public-send
180
+ Enabled: false
181
+ Style/SpecialGlobalVars:
182
+ Description: Avoid Perl-style global variables.
183
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms
184
+ Enabled: false
185
+ Style/VariableInterpolation:
186
+ Description: Don't interpolate global, instance and class variables directly in
187
+ strings.
188
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#curlies-interpolate
189
+ Enabled: false
190
+ Style/WhenThen:
191
+ Description: Use when x then ... for one-line cases.
192
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#one-line-cases
193
+ Enabled: false
194
+ Lint/EachWithObjectArgument:
195
+ Description: Check for immutable argument given to each_with_object.
196
+ Enabled: true
197
+ Lint/HandleExceptions:
198
+ Description: Don't suppress exception.
199
+ StyleGuide: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions
200
+ Enabled: false
201
+ Lint/LiteralInCondition:
202
+ Description: Checks of literals used in conditions.
203
+ Enabled: false
204
+ Lint/LiteralInInterpolation:
205
+ Description: Checks for literals used in interpolation.
206
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 0.2.8
2
+
3
+ * Merged Client and TalkClient: `client.discussions` is now just on Client. TalkClient is still present as an alias for Client
4
+ * Add `client.create_comment` to post a comment in a talk discussions
5
+ * Add `client.current_user` to get information contained in the authentication token, if given
6
+ * Add `client.cellect_workflows` to get information on workflows using the cellect services
7
+ * Add `client.cellect_subjects` to get subject information for a given cellect workflow
8
+
1
9
  # 0.2.7
2
10
 
3
11
  * Add `TalkClient` to interact with the talk api.
@@ -0,0 +1,14 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvbmZ42QexoMJ1J9z47Y2
3
+ 7q5qvtE1mblpOnN7g4ItumcnoCv8xfdbhnzkHj/YBtqVoHhAVNwxSJKyQ4tZBehg
4
+ yYMRzLzBeWO8vG8V/NqM/tmUXca3rvPqt8I7zMoesSggawwz3/Gsa6KFC0OLjDh+
5
+ vE1HlqNX6usKiGmt6fv3KVU9cjtfREIieXgr36nkJDtIVJ2/hX1LY/v0+AEjZaFj
6
+ IzXSihNkjPnq2ymdm0YtV0Mmy1sK4qBY8c/zCWQ9lisILu+/Ix2YwVlDXeV9CErE
7
+ 31b2jkPUVfLbCyeGPEIbYJw2pSiFWkuDR2VZaq+qeUbAwDfeu1OwueMZMt54vSYF
8
+ AKOxqMJtkrf8hxLSBZepk9sTS20T7gaEUyBhyeycarQqY34+VwG6UqLSy2H/Mq7r
9
+ 5J8x7fnZPAtkFizcu/YDsELmD7Rw8SQ389NzApLi1jx8eSTZT+nqetU4FPs4CGCd
10
+ A/XMj2kAz/IA7LbV4hhbx1tTwTo88Xzk9knTknFv1/sE/j+KeUTeA9dE7EniIgOg
11
+ V01zobEXAP5Eyf3+OwYv+Qvq3cV2BvRZfEBhzj1RezVEh187hfCtCaSVf3YrEeL2
12
+ MM74OPotMaVyaJFxj/GxhrahOgSv4TbMYk93d+vcIa0clO7wXrJJXoWwi3XpQvk7
13
+ SwgqmRqSTqDuxJqlJAuVhmMCAwEAAQ==
14
+ -----END PUBLIC KEY-----
@@ -0,0 +1,14 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArqLiWfs+Nm3BBMu9bcma
3
+ JaFhfSpKoHwsFjzRzkdFO0uKXPkS5nJq3UD/ZORr31XKYmwlbhGBzvpBUSoTmsjj
4
+ AXXP9cOlX/opoojuII2JXoUPjZH+YDl4y4M7YzXLyVKYW0FYR2pBXyhtEL9Lx0Ny
5
+ Nw2DW6p7Z9RwoU46WkapjMWor2wAlpnUM4ngDM/7ysEg8oi9KExrXlI4MCH1sVqj
6
+ U++8zkIXarqyDnyVk94ZoGA8EEqjeK3s3HonGG/DZyoj7OMcRa81fm94Sage5PWd
7
+ sJfgUw8bcj3/h/boRIJSMwRv1IJVQxz2o44es6dN+12q3hpXBhCfVZWz3MgoJFpt
8
+ t3e+KHB3UDBEIo55aPgbUAeuWV1IrBqZIQWLZcce4pzgS+A+0JgeetwbzubjHfZu
9
+ 8FQ/d+29M8N+R+TOS2WzVXCKzEZtw1BZ8V595X1Rf9FcklnA6CH32LybQD4/4ALz
10
+ JGx2WaCa2246pgJzT7fIo/L/bgRuEsBfwMR+wcOVr7yroFrndEyw1eGv2RuFsLNH
11
+ JwOjF+MTg/wtvaI8AXSTCSeKfoljUndNsiPTO8gJmiAq4yWFiMP1fKm/uczN6PKy
12
+ rYdQDSMt3a8UXZbw+9DocHDD0VvDHg6zuaa75tZrb7WvGrO7kbhUrJYwNCAbD2kN
13
+ Ca6VZnNmmy57yB/+jhBSNkECAwEAAQ==
14
+ -----END PUBLIC KEY-----
@@ -1,19 +1,18 @@
1
- require "panoptes/concerns/common_client"
2
- require "panoptes/client/me"
3
- require "panoptes/client/projects"
4
- require "panoptes/client/subjects"
5
- require "panoptes/client/subject_sets"
6
- require "panoptes/client/user_groups"
7
- require "panoptes/client/workflows"
1
+ require 'panoptes/endpoints/json_api_endpoint'
2
+ require 'panoptes/endpoints/json_endpoint'
3
+
4
+ require 'panoptes/client/cellect'
5
+ require 'panoptes/client/comments'
6
+ require 'panoptes/client/discussions'
7
+ require 'panoptes/client/me'
8
+ require 'panoptes/client/projects'
9
+ require 'panoptes/client/subject_sets'
10
+ require 'panoptes/client/subjects'
11
+ require 'panoptes/client/user_groups'
12
+ require 'panoptes/client/workflows'
8
13
 
9
14
  module Panoptes
10
15
  class Client
11
- class GenericError < StandardError; end
12
- class ConnectionFailed < GenericError; end
13
- class ResourceNotFound < GenericError; end
14
- class ServerError < GenericError; end
15
-
16
- include Panoptes::CommonClient
17
16
  include Panoptes::Client::Me
18
17
  include Panoptes::Client::Projects
19
18
  include Panoptes::Client::Subjects
@@ -21,73 +20,60 @@ module Panoptes
21
20
  include Panoptes::Client::UserGroups
22
21
  include Panoptes::Client::Workflows
23
22
 
24
- def get(path, query = {})
25
- response = conn.get("/api" + path, query)
26
- handle_response(response)
27
- end
28
-
29
- def post(path, body = {})
30
- response = conn.post("/api" + path, body)
31
- handle_response(response)
32
- end
23
+ include Panoptes::Client::Discussions
24
+ include Panoptes::Client::Comments
33
25
 
34
- def put(path, body = {}, etag: nil)
35
- headers = {}
36
- headers["If-Match"] = etag if etag
26
+ include Panoptes::Client::Cellect
37
27
 
38
- response = conn.put("/api" + path, body, headers)
39
- handle_response(response)
28
+ class GenericError < StandardError; end
29
+ class ConnectionFailed < GenericError; end
30
+ class ResourceNotFound < GenericError; end
31
+ class ServerError < GenericError; end
32
+ class NotLoggedIn < GenericError; end
33
+
34
+ attr_reader :env, :auth, :panoptes, :talk, :cellect
35
+
36
+ def initialize(env: :production, auth: {}, public_key_path: nil)
37
+ @env = env
38
+ @auth = auth
39
+ @public_key_path = public_key_path
40
+ @panoptes = Panoptes::Endpoints::JsonApiEndpoint.new(
41
+ auth: auth, url: panoptes_url, prefix: '/api'
42
+ )
43
+ @talk = Panoptes::Endpoints::JsonApiEndpoint.new(
44
+ auth: auth, url: talk_url
45
+ )
46
+ @cellect = Panoptes::Endpoints::JsonEndpoint.new(
47
+ url: panoptes_url, prefix: '/cellect'
48
+ )
40
49
  end
41
50
 
42
- def patch(path, body = {}, etag: nil)
43
- headers = {}
44
- headers["If-Match"] = etag if etag
51
+ def current_user
52
+ raise NotLoggedIn unless @auth[:token]
45
53
 
46
- response = conn.patch("/api" + path, body, headers)
47
- handle_response(response)
54
+ payload, = JWT.decode @auth[:token], jwt_signing_public_key, algorithm: 'RS512'
55
+ payload.fetch('data')
48
56
  end
49
57
 
50
- def delete(path, query = {}, etag: nil)
51
- headers = {}
52
- headers["If-Match"] = etag if etag
53
-
54
- response = conn.delete("/api" + path, query, headers)
55
- handle_response(response)
58
+ def jwt_signing_public_key
59
+ @jwt_signing_public_key ||= OpenSSL::PKey::RSA.new(File.read(@public_key_path))
56
60
  end
57
61
 
58
- # Get a path and perform automatic depagination
59
- def paginate(path, query, resource: nil)
60
- resource = path.split("/").last if resource.nil?
61
- data = last_response = get(path, query)
62
-
63
- while next_path = last_response["meta"][resource]["next_href"]
64
- last_response = get(next_path, query)
65
- if block_given?
66
- yield data, last_response
67
- else
68
- data[resource].concat(last_response[resource]) if data[resource].is_a?(Array)
69
- data["meta"][resource].merge!(last_response["meta"][resource])
70
- data["links"].merge!(last_response["links"])
71
- end
62
+ def panoptes_url
63
+ case env
64
+ when :production, 'production'.freeze
65
+ 'https://panoptes.zooniverse.org'.freeze
66
+ else
67
+ 'https://panoptes-staging.zooniverse.org'.freeze
72
68
  end
73
-
74
- data
75
- end
76
-
77
- private
78
-
79
- def conn
80
- @conn
81
69
  end
82
70
 
83
- def handle_response(response)
84
- case response.status
85
- when 404
86
- raise ResourceNotFound, status: response.status, body: response.body
87
- when 400..600
88
- raise ServerError.new(response.body)
71
+ def talk_url
72
+ case env
73
+ when :production, 'production'.freeze
74
+ 'https://talk.zooniverse.org'.freeze
89
75
  else
90
- response.body
76
+ 'https://talk-staging.zooniverse.org'.freeze
91
77
  end
92
78
  end
93
79
  end
@@ -0,0 +1,22 @@
1
+ require 'faraday'
2
+
3
+ module Panoptes
4
+ class Client
5
+ module Cellect
6
+ # Fetches all cellect-enabled, launched workflows.
7
+ #
8
+ # @return [Hash] the list of workflows
9
+ def cellect_workflows
10
+ cellect.get '/workflows'
11
+ end
12
+
13
+ # Fetches all active subjects for a cellect-enabled workflow.
14
+ #
15
+ # @param workflow_id [Integer] the id of the workflow
16
+ # @return [Hash] the list of subjects
17
+ def cellect_subjects(workflow_id)
18
+ cellect.get '/subjects', workflow_id: workflow_id
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ module Panoptes
2
+ class Client
3
+ module Comments
4
+ # Post a comment to a talk discussion
5
+ #
6
+ # @param discussion_id [Integer] filter by focussable id
7
+ # @param focus_type [String] filter by focussable type
8
+ # @return list of discussions
9
+ def create_comment(discussion_id:, body:)
10
+ user_id = current_user["id"]
11
+ response = talk.post("/comments", comments: {discussion_id: discussion_id, body: body, user_id: user_id})
12
+ response.fetch("comments")[0]
13
+ end
14
+ end
15
+ end
16
+ end
@@ -11,7 +11,7 @@ module Panoptes
11
11
  query[:focus_id] = focus_id
12
12
  query[:focus_type] = focus_type
13
13
 
14
- response = get("/discussions", query)
14
+ response = talk.get("/discussions", query)
15
15
  response.fetch("discussions")
16
16
  end
17
17
  end
@@ -1,8 +1,11 @@
1
+ require 'openssl'
2
+ require 'jwt'
3
+
1
4
  module Panoptes
2
5
  class Client
3
6
  module Me
4
7
  def me
5
- get("/me")["users"][0]
8
+ panoptes.get("/me")["users"][0]
6
9
  end
7
10
  end
8
11
  end
@@ -10,7 +10,7 @@ module Panoptes
10
10
  params = {}
11
11
  params[:search] = search if search
12
12
 
13
- paginate("/projects", params)["projects"]
13
+ panoptes.paginate("/projects", params)["projects"]
14
14
  end
15
15
 
16
16
  # Starts a background process to generate a new CSV export of all the classifications in the project.
@@ -19,7 +19,7 @@ module Panoptes
19
19
  # @return [Hash] the medium information where the export will be stored when it's generated
20
20
  def create_classifications_export(project_id)
21
21
  params = {media: {content_type: "text/csv", metadata: { recipients: []}}}
22
- post("/projects/#{project_id}/classifications_export", params)["media"].first
22
+ panoptes.post("/projects/#{project_id}/classifications_export", params)["media"].first
23
23
  end
24
24
 
25
25
  # Starts a background process to generate a new CSV export of all the subjects in the project.
@@ -28,7 +28,7 @@ module Panoptes
28
28
  # @return [Hash] the medium information where the export will be stored when it's generated
29
29
  def create_subjects_export(project_id)
30
30
  params = {media: {content_type: "text/csv", metadata: { recipients: []}}}
31
- post("/projects/#{project_id}/subjects_export", params)["media"].first
31
+ panoptes.post("/projects/#{project_id}/subjects_export", params)["media"].first
32
32
  end
33
33
 
34
34
  # Starts a background process to generate a new CSV export of all the workflows in the project.
@@ -37,7 +37,7 @@ module Panoptes
37
37
  # @return [Hash] the medium information where the export will be stored when it's generated
38
38
  def create_workflows_export(project_id)
39
39
  params = {media: {content_type: "text/csv", metadata: { recipients: []}}}
40
- post("/projects/#{project_id}/workflows_export", params)["media"].first
40
+ panoptes.post("/projects/#{project_id}/workflows_export", params)["media"].first
41
41
  end
42
42
 
43
43
  # Starts a background process to generate a new CSV export of all the workflow_contents in the project.
@@ -46,7 +46,7 @@ module Panoptes
46
46
  # @return [Hash] the medium information where the export will be stored when it's generated
47
47
  def create_workflow_contents_export(project_id)
48
48
  params = {media: {content_type: "text/csv", metadata: { recipients: []}}}
49
- post("/projects/#{project_id}/workflow_contents_export", params)["media"].first
49
+ panoptes.post("/projects/#{project_id}/workflow_contents_export", params)["media"].first
50
50
  end
51
51
 
52
52
  # Starts a background process to generate a new CSV export of the aggretation results of the project.
@@ -55,7 +55,7 @@ module Panoptes
55
55
  # @return [Hash] the medium information where the export will be stored when it's generated
56
56
  def create_aggregations_export(project_id)
57
57
  params = {media: {content_type: "application/x-gzip", metadata: { recipients: []}}}
58
- post("/projects/#{project_id}/aggregations_export", params)["media"].first
58
+ panoptes.post("/projects/#{project_id}/aggregations_export", params)["media"].first
59
59
  end
60
60
  end
61
61
  end
@@ -2,25 +2,25 @@ module Panoptes
2
2
  class Client
3
3
  module SubjectSets
4
4
  def subject_set(subject_set_id)
5
- response = get("/subject_sets/#{subject_set_id}")
5
+ response = panoptes.get("/subject_sets/#{subject_set_id}")
6
6
  response.fetch("subject_sets").find {|i| i.fetch("id").to_s == subject_set_id.to_s }
7
7
  end
8
8
 
9
9
  def create_subject_set(attributes)
10
- response = post("/subject_sets", subject_sets: attributes)
10
+ response = panoptes.post("/subject_sets", subject_sets: attributes)
11
11
  response.fetch("subject_sets").first
12
12
  end
13
13
 
14
14
  def update_subject_set(subject_set_id, attributes)
15
- response = conn.get("/api/subject_sets/#{subject_set_id}")
15
+ response = panoptes.connection.get("/api/subject_sets/#{subject_set_id}")
16
16
  etag = response.headers["ETag"]
17
17
 
18
- response = put("/subject_sets/#{subject_set_id}", {subject_sets: attributes}, etag: etag)
18
+ response = panoptes.put("/subject_sets/#{subject_set_id}", {subject_sets: attributes}, etag: etag)
19
19
  response.fetch("subject_sets").first
20
20
  end
21
21
 
22
22
  def add_subjects_to_subject_set(subject_set_id, subject_ids)
23
- response = post("/subject_sets/#{subject_set_id}/links/subjects", subjects: subject_ids)
23
+ response = panoptes.post("/subject_sets/#{subject_set_id}/links/subjects", subjects: subject_ids)
24
24
  true
25
25
  end
26
26
  end
@@ -9,7 +9,7 @@ module Panoptes
9
9
  query = {}
10
10
  query[:subject_set_id] = subject_set_id
11
11
 
12
- response = get("/subjects", query)
12
+ response = panoptes.get("/subjects", query)
13
13
  response.fetch("subjects")
14
14
  end
15
15
 
@@ -20,7 +20,7 @@ module Panoptes
20
20
  # @param subject_id [Integer] the ID of a subject associated with that workflow (through one of the assigned subject_sets)
21
21
  # @return nothing
22
22
  def retire_subject(workflow_id, subject_id, reason: nil)
23
- post("/workflows/#{workflow_id}/retired_subjects", {
23
+ panoptes.post("/workflows/#{workflow_id}/retired_subjects", {
24
24
  admin: true,
25
25
  subject_id: subject_id,
26
26
  retirement_reason: reason
@@ -7,31 +7,31 @@ module Panoptes
7
7
  # @param name [String] The name of the user group. Must be unique for the entirity of Zooniverse.
8
8
  # @return [Hash] The created user group.
9
9
  def create_user_group(name)
10
- post("/user_groups", user_groups: {
10
+ panoptes.post("/user_groups", user_groups: {
11
11
  name: name
12
12
  })["user_groups"][0]
13
13
  end
14
14
 
15
15
  def user_groups
16
- get("/user_groups")["user_groups"]
16
+ panoptes.get("/user_groups")["user_groups"]
17
17
  end
18
18
 
19
19
  def join_user_group(user_group_id, user_id, join_token:)
20
- post("/memberships", memberships: {
20
+ panoptes.post("/memberships", memberships: {
21
21
  join_token: join_token,
22
22
  links: {user: user_id, user_group: user_group_id}
23
23
  })["memberships"][0]
24
24
  end
25
25
 
26
26
  def remove_user_from_user_group(user_group_id, user_id)
27
- delete("/user_groups/#{user_group_id}/links/users/#{user_id}")
27
+ panoptes.delete("/user_groups/#{user_group_id}/links/users/#{user_id}")
28
28
  end
29
29
 
30
30
  def delete_user_group(user_group_id)
31
- response = conn.get("/api/user_groups/#{user_group_id}")
31
+ response = panoptes.connection.get("/api/user_groups/#{user_group_id}")
32
32
  etag = response.headers["ETag"]
33
33
 
34
- delete("/user_groups/#{user_group_id}", {}, etag: etag)
34
+ panoptes.delete("/user_groups/#{user_group_id}", {}, etag: etag)
35
35
  end
36
36
  end
37
37
  end
@@ -1,5 +1,5 @@
1
1
  module Panoptes
2
2
  class Client
3
- VERSION = "0.2.7"
3
+ VERSION = "0.2.8".freeze
4
4
  end
5
5
  end
@@ -2,12 +2,12 @@ module Panoptes
2
2
  class Client
3
3
  module Workflows
4
4
  def workflow(workflow_id)
5
- response = get("/workflows/#{workflow_id}")
5
+ response = panoptes.get("/workflows/#{workflow_id}")
6
6
  response.fetch("workflows").find {|i| i.fetch("id").to_s == workflow_id.to_s }
7
7
  end
8
8
 
9
9
  def create_workflow(attributes)
10
- response = post("/workflows", workflows: attributes)
10
+ response = panoptes.post("/workflows", workflows: attributes)
11
11
  response.fetch("workflows").first
12
12
  end
13
13
  end
@@ -0,0 +1,102 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'faraday/panoptes'
4
+
5
+ module Panoptes
6
+ module Endpoints
7
+ class BaseEndpoint
8
+ attr_reader :auth, :url, :prefix
9
+
10
+ # @param auth [Hash<token: String, client_id: String, client_secret: String>] Authentication details
11
+ # * either nothing,
12
+ # * a hash with +:token+ (an existing OAuth user token),
13
+ # * or a hash with +:client_id+ and +:client_secret+
14
+ # (a keypair for an OAuth Application).
15
+ # @param url [String] API location to use.
16
+ # @param prefix [String] An optional API url prefix
17
+ # @yield Allows an optional block to configure the faraday connection
18
+ # @yieldparam faraday [Faraday::Connection] The faraday connection
19
+ def initialize(auth: {}, url: nil, prefix: nil, &config)
20
+ @auth = auth
21
+ @url = url
22
+ @prefix = prefix
23
+ @config = config
24
+ end
25
+
26
+ def connection
27
+ @connection ||= Faraday.new(url) do |faraday|
28
+ auth_request faraday, auth
29
+ configure faraday
30
+ end
31
+ end
32
+
33
+ def get(path, query = {})
34
+ request :get, path, query
35
+ end
36
+
37
+ def post(path, body = {})
38
+ request :post, path, body
39
+ end
40
+
41
+ def put(path, body = {}, etag: nil)
42
+ request :put, path, body, etag_header(etag)
43
+ end
44
+
45
+ def patch(path, body = {}, etag: nil)
46
+ request :patch, path, body, etag_header(etag)
47
+ end
48
+
49
+ def delete(path, query = {}, etag: nil)
50
+ request :delete, path, query, etag_header(etag)
51
+ end
52
+
53
+ def etag_header(etag)
54
+ {}.tap do |headers|
55
+ headers['If-Match'] = etag if etag
56
+ end
57
+ end
58
+
59
+ def request(method, path, *args)
60
+ path = "#{prefix}/#{path}" if prefix
61
+ handle_response connection.send(method, path, *args)
62
+ end
63
+
64
+ def handle_response(response)
65
+ case response.status
66
+ when 404
67
+ raise ResourceNotFound, status: response.status, body: response.body
68
+ when 400..600
69
+ raise ServerError.new, response.body
70
+ else
71
+ response.body
72
+ end
73
+ end
74
+
75
+ protected
76
+
77
+ def configure(faraday)
78
+ if @config
79
+ @config.call faraday
80
+ else
81
+ faraday.request :panoptes_api_v1
82
+ faraday.request :json
83
+ faraday.response :json
84
+ faraday.adapter Faraday.default_adapter
85
+ end
86
+ end
87
+
88
+ def auth_request(faraday, auth)
89
+ if auth[:token]
90
+ faraday.request :panoptes_access_token,
91
+ url: url,
92
+ access_token: auth[:token]
93
+ elsif auth[:client_id] && auth[:client_secret]
94
+ faraday.request :panoptes_client_credentials,
95
+ url: url,
96
+ client_id: auth[:client_id],
97
+ client_secret: auth[:client_secret]
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'base_endpoint'
2
+
3
+ module Panoptes
4
+ module Endpoints
5
+ class JsonApiEndpoint < BaseEndpoint
6
+ # Get a path and perform automatic depagination
7
+ def paginate(path, query, resource: nil)
8
+ resource = path.split('/').last if resource.nil?
9
+ data = last_response = get(path, query)
10
+
11
+ while next_path = last_response['meta'][resource]['next_href']
12
+ last_response = get(next_path, query)
13
+ if block_given?
14
+ yield data, last_response
15
+ else
16
+ data[resource].concat(last_response[resource]) if data[resource].is_a?(Array)
17
+ data['meta'][resource].merge!(last_response['meta'][resource])
18
+ data['links'].merge!(last_response['links'])
19
+ end
20
+ end
21
+
22
+ data
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ require_relative 'base_endpoint'
2
+
3
+ module Panoptes
4
+ module Endpoints
5
+ class JsonEndpoint < BaseEndpoint
6
+ # Automatically configured connection to use JSON requests/responses
7
+ # @see Panoptes::Endpoints::BaseEndpoint#initialize
8
+ def initialize(auth: {}, url: nil, prefix: nil, &config)
9
+ super auth: auth, url: url, prefix: prefix do |faraday|
10
+ config&.call faraday
11
+ faraday.request :json
12
+ faraday.response :json
13
+ faraday.adapter Faraday.default_adapter
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ module Panoptes
2
+ class Session
3
+ def initialize(auth)
4
+ end
5
+ end
6
+ end
@@ -1,25 +1,5 @@
1
- require "panoptes/concerns/common_client"
2
- require "panoptes/client/discussions"
3
-
4
1
  module Panoptes
5
- class TalkClient
6
- include Panoptes::CommonClient
7
- include Panoptes::Client::Discussions
8
-
9
- # @param auth [Hash] Authentication details
10
- # * either nothing,
11
- # * a hash with +:token+ (an existing OAuth user token),
12
- # * or a hash with +:client_id+ and +:client_secret+ (a keypair for an OAuth Application).
13
- # A client is the main interface to the talk v2 API.
14
- # @param url [String] Optional override for the API location to use. Defaults to the official talk api production environment.
15
- # @param auth_url [String] Optional override for the auth API location to use. Defaults to the official api production environment.
16
- def initialize(auth: {}, url: PROD_TALK_API_URL, auth_url: PROD_API_URL)
17
- super(auth: auth, url: url, auth_url: auth_url)
18
- end
19
-
20
- def get(path, query = {})
21
- response = conn.get(path, query)
22
- handle_response(response)
23
- end
2
+ # Backwards compat
3
+ class TalkClient < Client
24
4
  end
25
5
  end
@@ -20,9 +20,11 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency "faraday"
22
22
  spec.add_dependency "faraday-panoptes", "~> 0.2.0"
23
+ spec.add_dependency "jwt", "~> 1.5.0"
23
24
 
24
25
  spec.add_development_dependency "bundler", "~> 1.11"
25
26
  spec.add_development_dependency "rake", "~> 10.0"
26
27
  spec.add_development_dependency "rspec", "~> 3.0"
28
+ spec.add_development_dependency "timecop", "~> 0.8.0"
27
29
  spec.add_development_dependency "yard"
28
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: panoptes-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marten Veldthuis
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-08-03 00:00:00.000000000 Z
11
+ date: 2016-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: jwt
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.5.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.5.0
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,20 @@ dependencies:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
96
  version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: timecop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.8.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.8.0
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: yard
85
113
  requirement: !ruby/object:Gem::Requirement
@@ -102,7 +130,9 @@ extensions: []
102
130
  extra_rdoc_files: []
103
131
  files:
104
132
  - ".gitignore"
133
+ - ".hound.yml"
105
134
  - ".rspec"
135
+ - ".ruby-style.yml"
106
136
  - ".travis.yml"
107
137
  - CHANGELOG.md
108
138
  - CODE_OF_CONDUCT.md
@@ -112,8 +142,12 @@ files:
112
142
  - Rakefile
113
143
  - bin/console
114
144
  - bin/setup
145
+ - data/doorkeeper-jwt-production.pub
146
+ - data/doorkeeper-jwt-staging.pub
115
147
  - lib/panoptes-client.rb
116
148
  - lib/panoptes/client.rb
149
+ - lib/panoptes/client/cellect.rb
150
+ - lib/panoptes/client/comments.rb
117
151
  - lib/panoptes/client/discussions.rb
118
152
  - lib/panoptes/client/me.rb
119
153
  - lib/panoptes/client/projects.rb
@@ -122,7 +156,10 @@ files:
122
156
  - lib/panoptes/client/user_groups.rb
123
157
  - lib/panoptes/client/version.rb
124
158
  - lib/panoptes/client/workflows.rb
125
- - lib/panoptes/concerns/common_client.rb
159
+ - lib/panoptes/endpoints/base_endpoint.rb
160
+ - lib/panoptes/endpoints/json_api_endpoint.rb
161
+ - lib/panoptes/endpoints/json_endpoint.rb
162
+ - lib/panoptes/session.rb
126
163
  - lib/panoptes/talk_client.rb
127
164
  - panoptes-client.gemspec
128
165
  homepage: https://github.com/zooniverse/panoptes-client.rb
@@ -1,77 +0,0 @@
1
- require 'faraday'
2
- require 'faraday_middleware'
3
- require 'faraday/panoptes'
4
-
5
- require "panoptes/client/version"
6
-
7
- module Panoptes
8
- module CommonClient
9
- PROD_API_URL = "https://panoptes.zooniverse.org".freeze
10
- PROD_TALK_API_URL = "https://talk.zooniverse.org".freeze
11
-
12
- # A client is the main interface to the API.
13
- #
14
- # @param auth [Hash] Authentication details
15
- # * either nothing,
16
- # * a hash with +:token+ (an existing OAuth user token),
17
- # * or a hash with +:client_id+ and +:client_secret+ (a keypair for an OAuth Application).
18
- # @param url [String] API location to use.
19
- # @param auth_url [String] Auth API location to use.
20
- def initialize(auth: {}, url: PROD_API_URL, auth_url: PROD_API_URL)
21
- @conn = Faraday.new(url: url) do |faraday|
22
- case
23
- when auth[:token]
24
- faraday.request :panoptes_access_token,
25
- url: auth_url,
26
- access_token: auth[:token]
27
- when auth[:client_id] && auth[:client_secret]
28
- faraday.request :panoptes_client_credentials,
29
- url: auth_url,
30
- client_id: auth[:client_id],
31
- client_secret: auth[:client_secret]
32
- end
33
-
34
- faraday.request :panoptes_api_v1
35
- faraday.request :json
36
- faraday.response :json
37
- faraday.adapter Faraday.default_adapter
38
- end
39
- end
40
-
41
- # Get a path and perform automatic depagination
42
- def paginate(path, query, resource: nil)
43
- resource = path.split("/").last if resource.nil?
44
- data = last_response = get(path, query)
45
-
46
- while next_path = last_response["meta"][resource]["next_href"]
47
- last_response = get(next_path, query)
48
- if block_given?
49
- yield data, last_response
50
- else
51
- data[resource].concat(last_response[resource]) if data[resource].is_a?(Array)
52
- data["meta"][resource].merge!(last_response["meta"][resource])
53
- data["links"].merge!(last_response["links"])
54
- end
55
- end
56
-
57
- data
58
- end
59
-
60
- private
61
-
62
- def conn
63
- @conn
64
- end
65
-
66
- def handle_response(response)
67
- case response.status
68
- when 404
69
- raise ResourceNotFound, status: response.status, body: response.body
70
- when 400..600
71
- raise ServerError.new(response.body)
72
- else
73
- response.body
74
- end
75
- end
76
- end
77
- end