panoptes-client 0.2.7 → 0.2.8

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 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