Class: CardDB::Resources::Scans

Inherits:
Base
  • Object
show all
Defined in:
lib/carddb/resources/scans.rb

Overview

Scan job resource for GraphQL-first card scanning workflows.

Constant Summary collapse

TERMINAL_STATUSES =
%w[completed failed cancelled].freeze
CREATE_JOB_KEY_MAP =
{
  publisher_slug: 'publisherSlug',
  game_key: 'gameKey',
  dataset_key: 'datasetKey',
  file_id: 'fileId',
  client_request_id: 'clientRequestId',
  template_id: 'templateId',
  template_version_id: 'templateVersionId',
  client_ocr_hints: 'clientOcrHints',
  client_normalization: 'clientNormalization',
  webhook_url: 'webhookUrl',
  webhook_secret: 'webhookSecret'
}.freeze
UPLOAD_SESSION_KEY_MAP =
{
  publisher_slug: 'publisherSlug',
  game_key: 'gameKey',
  dataset_key: 'datasetKey',
  content_type: 'contentType'
}.freeze
CONFIRM_UPLOAD_KEY_MAP =
{
  publisher_slug: 'publisherSlug',
  game_key: 'gameKey',
  dataset_key: 'datasetKey',
  file_id: 'fileId'
}.freeze
FEEDBACK_KEY_MAP =
{
  job_id: 'jobId',
  selected_record_id: 'selectedRecordId'
}.freeze
METRICS_KEY_MAP =
{
  publisher_slug: 'publisherSlug',
  game_key: 'gameKey',
  dataset_key: 'datasetKey'
}.freeze

Instance Attribute Summary

Attributes inherited from Base

#client, #config, #connection

Instance Method Summary collapse

Methods inherited from Base

#initialize

Constructor Details

This class inherits a constructor from CardDB::Resources::Base

Instance Method Details

#confirm_upload(input: nil, **params) ⇒ Object

Confirm that a scan image upload completed and return the uploaded file.



59
60
61
62
63
64
# File 'lib/carddb/resources/scans.rb', line 59

def confirm_upload(input: nil, **params)
  request = scoped_input(graphql_input(input || params, CONFIRM_UPLOAD_KEY_MAP))
  data = connection.execute(QueryBuilder.confirm_scan_upload, { input: request })

  File.new(data['confirmScanUpload'], client: client)
end

#create_job(input: nil, **params) ⇒ Object

Create a scan job for an already uploaded image file.



67
68
69
70
71
72
# File 'lib/carddb/resources/scans.rb', line 67

def create_job(input: nil, **params)
  request = scoped_input(graphql_input(input || params, CREATE_JOB_KEY_MAP))
  data = connection.execute(QueryBuilder.create_scan_job, { input: request })

  ScanJobPayload.new(data['createScanJob'], client: client)
end

#create_upload_session(input: nil, **params) ⇒ Object

Create a scan image upload session using a scan-scoped credential.



51
52
53
54
55
56
# File 'lib/carddb/resources/scans.rb', line 51

def create_upload_session(input: nil, **params)
  request = scoped_input(graphql_input(input || params, UPLOAD_SESSION_KEY_MAP))
  data = connection.execute(QueryBuilder.create_scan_upload_session, { input: request })

  PresignedUpload.new(data['createScanUploadSession'], client: client)
end

#get_job(id, cache: nil) ⇒ Object

Get a scan job and its latest processing result.



75
76
77
78
79
80
81
# File 'lib/carddb/resources/scans.rb', line 75

def get_job(id, cache: nil)
  key = cache_key('scans', 'get_job', id: id)
  with_cache(key, resource: :scans, cache: cache) do
    data = connection.execute(QueryBuilder.scan_job, { id: id })
    data['scanJob'] ? ScanJob.new(data['scanJob'], client: client) : nil
  end
end

#metrics(input: nil, **params) ⇒ Object

Aggregate scan metrics for a publisher/game/dataset scope.



113
114
115
116
117
118
# File 'lib/carddb/resources/scans.rb', line 113

def metrics(input: nil, **params)
  request = scoped_input(graphql_input(input || params, METRICS_KEY_MAP))
  data = connection.execute(QueryBuilder.scan_metrics, { input: request })

  ScanMetrics.new(data['scanMetrics'], client: client)
end

#poll_job(id, timeout: 300, interval: 1, backoff: 1.5, max_interval: 10, cancel: nil) ⇒ Object

Poll a scan job until it reaches a terminal status.



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/carddb/resources/scans.rb', line 84

def poll_job(id, timeout: 300, interval: 1, backoff: 1.5, max_interval: 10, cancel: nil)
  started_at = monotonic_time
  current_interval = [interval.to_f, 0.001].max
  max_interval = [max_interval.to_f, current_interval].max

  loop do
    raise Error, 'scan job polling cancelled' if cancel&.call

    job = get_job(id, cache: false)
    raise NotFoundError, "scan job '#{id}' was not found" unless job
    return job if TERMINAL_STATUSES.include?(job.status.to_s.downcase)

    remaining = timeout.to_f - (monotonic_time - started_at)
    raise TimeoutError, "timed out waiting for scan job '#{id}'" if remaining <= 0

    sleep([current_interval, remaining].min)
    current_interval = [current_interval * [backoff.to_f, 1.0].max, max_interval].min
  end
end

#submit_feedback(input: nil, **params) ⇒ Object

Submit correctness feedback for a completed scan job.



105
106
107
108
109
110
# File 'lib/carddb/resources/scans.rb', line 105

def submit_feedback(input: nil, **params)
  request = graphql_input(input || params, FEEDBACK_KEY_MAP)
  data = connection.execute(QueryBuilder.submit_scan_feedback, { input: request })

  ScanFeedbackPayload.new(data['submitScanFeedback'], client: client)
end

#verify_webhook_signature(body:, signature:, secret:) ⇒ Object

Verify an X-CardDB-Signature header value for a webhook body. rubocop:disable Naming/PredicateMethod



122
123
124
125
126
# File 'lib/carddb/resources/scans.rb', line 122

def verify_webhook_signature(body:, signature:, secret:)
  expected = "sha256=#{OpenSSL::HMAC.hexdigest('SHA256', secret.to_s, body.to_s)}"

  secure_compare?(signature.to_s.strip.downcase, expected)
end