carddb 0.3.10 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec_status +125 -108
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +3 -0
- data/README.md +151 -4
- data/carddb.gemspec +6 -5
- data/examples/scan_workflow.rb +49 -0
- data/lib/carddb/client.rb +14 -0
- data/lib/carddb/collection.rb +267 -6
- data/lib/carddb/configuration.rb +1 -1
- data/lib/carddb/deck_tokens.rb +197 -0
- data/lib/carddb/filter_builder.rb +4 -4
- data/lib/carddb/query_builder.rb +309 -0
- data/lib/carddb/resources/files.rb +75 -0
- data/lib/carddb/resources/games.rb +19 -0
- data/lib/carddb/resources/records.rb +4 -4
- data/lib/carddb/resources/rules.rb +6 -6
- data/lib/carddb/resources/scan_templates.rb +128 -0
- data/lib/carddb/resources/scans.rb +133 -0
- data/lib/carddb/version.rb +1 -1
- data/lib/carddb.rb +19 -2
- metadata +25 -7
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
|
|
5
|
+
module CardDB
|
|
6
|
+
module Resources
|
|
7
|
+
# Scan job resource for GraphQL-first card scanning workflows.
|
|
8
|
+
class Scans < Base
|
|
9
|
+
TERMINAL_STATUSES = %w[completed failed cancelled].freeze
|
|
10
|
+
|
|
11
|
+
CREATE_JOB_KEY_MAP = {
|
|
12
|
+
publisher_slug: 'publisherSlug',
|
|
13
|
+
game_key: 'gameKey',
|
|
14
|
+
dataset_key: 'datasetKey',
|
|
15
|
+
file_id: 'fileId',
|
|
16
|
+
client_request_id: 'clientRequestId',
|
|
17
|
+
template_id: 'templateId',
|
|
18
|
+
template_version_id: 'templateVersionId',
|
|
19
|
+
client_ocr_hints: 'clientOcrHints',
|
|
20
|
+
client_normalization: 'clientNormalization',
|
|
21
|
+
webhook_url: 'webhookUrl',
|
|
22
|
+
webhook_secret: 'webhookSecret'
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
FEEDBACK_KEY_MAP = {
|
|
26
|
+
job_id: 'jobId',
|
|
27
|
+
selected_record_id: 'selectedRecordId'
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
METRICS_KEY_MAP = {
|
|
31
|
+
publisher_slug: 'publisherSlug',
|
|
32
|
+
game_key: 'gameKey',
|
|
33
|
+
dataset_key: 'datasetKey'
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
# Create a scan job for an already uploaded image file.
|
|
37
|
+
def create_job(input: nil, **params)
|
|
38
|
+
request = scoped_input(graphql_input(input || params, CREATE_JOB_KEY_MAP))
|
|
39
|
+
data = connection.execute(QueryBuilder.create_scan_job, { input: request })
|
|
40
|
+
|
|
41
|
+
ScanJobPayload.new(data['createScanJob'], client: client)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get a scan job and its latest processing result.
|
|
45
|
+
def get_job(id, cache: nil)
|
|
46
|
+
key = cache_key('scans', 'get_job', id: id)
|
|
47
|
+
with_cache(key, resource: :scans, cache: cache) do
|
|
48
|
+
data = connection.execute(QueryBuilder.scan_job, { id: id })
|
|
49
|
+
data['scanJob'] ? ScanJob.new(data['scanJob'], client: client) : nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Poll a scan job until it reaches a terminal status.
|
|
54
|
+
def poll_job(id, timeout: 300, interval: 1, backoff: 1.5, max_interval: 10, cancel: nil)
|
|
55
|
+
started_at = monotonic_time
|
|
56
|
+
current_interval = [interval.to_f, 0.001].max
|
|
57
|
+
max_interval = [max_interval.to_f, current_interval].max
|
|
58
|
+
|
|
59
|
+
loop do
|
|
60
|
+
raise Error, 'scan job polling cancelled' if cancel&.call
|
|
61
|
+
|
|
62
|
+
job = get_job(id, cache: false)
|
|
63
|
+
raise NotFoundError, "scan job '#{id}' was not found" unless job
|
|
64
|
+
return job if TERMINAL_STATUSES.include?(job.status.to_s.downcase)
|
|
65
|
+
|
|
66
|
+
remaining = timeout.to_f - (monotonic_time - started_at)
|
|
67
|
+
raise TimeoutError, "timed out waiting for scan job '#{id}'" if remaining <= 0
|
|
68
|
+
|
|
69
|
+
sleep([current_interval, remaining].min)
|
|
70
|
+
current_interval = [current_interval * [backoff.to_f, 1.0].max, max_interval].min
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Submit correctness feedback for a completed scan job.
|
|
75
|
+
def submit_feedback(input: nil, **params)
|
|
76
|
+
request = graphql_input(input || params, FEEDBACK_KEY_MAP)
|
|
77
|
+
data = connection.execute(QueryBuilder.submit_scan_feedback, { input: request })
|
|
78
|
+
|
|
79
|
+
ScanFeedbackPayload.new(data['submitScanFeedback'], client: client)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Aggregate scan metrics for a publisher/game/dataset scope.
|
|
83
|
+
def metrics(input: nil, **params)
|
|
84
|
+
request = scoped_input(graphql_input(input || params, METRICS_KEY_MAP))
|
|
85
|
+
data = connection.execute(QueryBuilder.scan_metrics, { input: request })
|
|
86
|
+
|
|
87
|
+
ScanMetrics.new(data['scanMetrics'], client: client)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Verify an X-CardDB-Signature header value for a webhook body.
|
|
91
|
+
# rubocop:disable Naming/PredicateMethod
|
|
92
|
+
def verify_webhook_signature(body:, signature:, secret:)
|
|
93
|
+
expected = "sha256=#{OpenSSL::HMAC.hexdigest('SHA256', secret.to_s, body.to_s)}"
|
|
94
|
+
|
|
95
|
+
secure_compare?(signature.to_s.strip.downcase, expected)
|
|
96
|
+
end
|
|
97
|
+
# rubocop:enable Naming/PredicateMethod
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def scoped_input(input)
|
|
102
|
+
publisher_slug = input['publisherSlug'] || resolve_publisher(nil)
|
|
103
|
+
game_key = input['gameKey'] || resolve_game(nil)
|
|
104
|
+
validate_access!(publisher_slug, game_key)
|
|
105
|
+
|
|
106
|
+
input.merge('publisherSlug' => publisher_slug, 'gameKey' => game_key)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def graphql_input(input, key_map)
|
|
110
|
+
input.each_with_object({}) do |(key, value), output|
|
|
111
|
+
next if value.nil?
|
|
112
|
+
|
|
113
|
+
output[key_map[key.to_sym] || key.to_s] = value
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def secure_compare?(left, right)
|
|
118
|
+
max_length = [left.bytesize, right.bytesize].max
|
|
119
|
+
diff = left.bytesize ^ right.bytesize
|
|
120
|
+
|
|
121
|
+
max_length.times do |index|
|
|
122
|
+
diff |= (left.getbyte(index) || 0) ^ (right.getbyte(index) || 0)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
diff.zero?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def monotonic_time
|
|
129
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
data/lib/carddb/version.rb
CHANGED
data/lib/carddb.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative 'carddb/query_builder'
|
|
|
8
8
|
require_relative 'carddb/filter_builder'
|
|
9
9
|
require_relative 'carddb/collection'
|
|
10
10
|
require_relative 'carddb/cache'
|
|
11
|
+
require_relative 'carddb/deck_tokens'
|
|
11
12
|
require_relative 'carddb/resources/base'
|
|
12
13
|
require_relative 'carddb/resources/publishers'
|
|
13
14
|
require_relative 'carddb/resources/games'
|
|
@@ -17,6 +18,8 @@ require_relative 'carddb/resources/import_formats'
|
|
|
17
18
|
require_relative 'carddb/resources/imports'
|
|
18
19
|
require_relative 'carddb/resources/exports'
|
|
19
20
|
require_relative 'carddb/resources/files'
|
|
21
|
+
require_relative 'carddb/resources/scans'
|
|
22
|
+
require_relative 'carddb/resources/scan_templates'
|
|
20
23
|
require_relative 'carddb/resources/decks'
|
|
21
24
|
require_relative 'carddb/resources/rules'
|
|
22
25
|
require_relative 'carddb/batch'
|
|
@@ -142,6 +145,20 @@ module CardDB
|
|
|
142
145
|
default_client.files
|
|
143
146
|
end
|
|
144
147
|
|
|
148
|
+
# Access the Scans resource via the default client.
|
|
149
|
+
#
|
|
150
|
+
# @return [Resources::Scans]
|
|
151
|
+
def scans
|
|
152
|
+
default_client.scans
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Access the Scan Templates resource via the default client.
|
|
156
|
+
#
|
|
157
|
+
# @return [Resources::ScanTemplates]
|
|
158
|
+
def scan_templates
|
|
159
|
+
default_client.scan_templates
|
|
160
|
+
end
|
|
161
|
+
|
|
145
162
|
# Access the Decks resource via the default client.
|
|
146
163
|
#
|
|
147
164
|
# @return [Resources::Decks]
|
|
@@ -185,8 +202,8 @@ module CardDB
|
|
|
185
202
|
# results[0] # => Game
|
|
186
203
|
# results[1] # => Game
|
|
187
204
|
# results[2] # => Publisher
|
|
188
|
-
def batch(&
|
|
189
|
-
default_client.batch(&
|
|
205
|
+
def batch(&)
|
|
206
|
+
default_client.batch(&)
|
|
190
207
|
end
|
|
191
208
|
end
|
|
192
209
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: carddb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- CardDB Team
|
|
@@ -9,6 +9,20 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ed25519
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.3'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.3'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: faraday
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -40,7 +54,7 @@ dependencies:
|
|
|
40
54
|
description: A Ruby client library for interacting with the CardDB GraphQL API. Search
|
|
41
55
|
and fetch card game data with an expressive filter DSL.
|
|
42
56
|
email:
|
|
43
|
-
- support@carddb.
|
|
57
|
+
- support@carddb.dev
|
|
44
58
|
executables: []
|
|
45
59
|
extensions: []
|
|
46
60
|
extra_rdoc_files: []
|
|
@@ -58,6 +72,7 @@ files:
|
|
|
58
72
|
- examples/filtering.rb
|
|
59
73
|
- examples/pagination.rb
|
|
60
74
|
- examples/publisher_content_pipeline.rb
|
|
75
|
+
- examples/scan_workflow.rb
|
|
61
76
|
- lib/carddb.rb
|
|
62
77
|
- lib/carddb/batch.rb
|
|
63
78
|
- lib/carddb/cache.rb
|
|
@@ -65,6 +80,7 @@ files:
|
|
|
65
80
|
- lib/carddb/collection.rb
|
|
66
81
|
- lib/carddb/configuration.rb
|
|
67
82
|
- lib/carddb/connection.rb
|
|
83
|
+
- lib/carddb/deck_tokens.rb
|
|
68
84
|
- lib/carddb/errors.rb
|
|
69
85
|
- lib/carddb/filter_builder.rb
|
|
70
86
|
- lib/carddb/query_builder.rb
|
|
@@ -79,15 +95,17 @@ files:
|
|
|
79
95
|
- lib/carddb/resources/publishers.rb
|
|
80
96
|
- lib/carddb/resources/records.rb
|
|
81
97
|
- lib/carddb/resources/rules.rb
|
|
98
|
+
- lib/carddb/resources/scan_templates.rb
|
|
99
|
+
- lib/carddb/resources/scans.rb
|
|
82
100
|
- lib/carddb/version.rb
|
|
83
101
|
- scripts/publish.sh
|
|
84
|
-
homepage: https://
|
|
102
|
+
homepage: https://carddb.dev
|
|
85
103
|
licenses:
|
|
86
104
|
- MIT
|
|
87
105
|
metadata:
|
|
88
|
-
homepage_uri: https://
|
|
89
|
-
source_code_uri: https://github.com/
|
|
90
|
-
changelog_uri: https://github.com/
|
|
106
|
+
homepage_uri: https://carddb.dev
|
|
107
|
+
source_code_uri: https://github.com/carddb/carddb-ruby
|
|
108
|
+
changelog_uri: https://github.com/carddb/carddb-ruby/blob/main/CHANGELOG.md
|
|
91
109
|
rubygems_mfa_required: 'true'
|
|
92
110
|
rdoc_options: []
|
|
93
111
|
require_paths:
|
|
@@ -96,7 +114,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
96
114
|
requirements:
|
|
97
115
|
- - ">="
|
|
98
116
|
- !ruby/object:Gem::Version
|
|
99
|
-
version: 3.
|
|
117
|
+
version: 3.2.0
|
|
100
118
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
119
|
requirements:
|
|
102
120
|
- - ">="
|