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
data/lib/carddb/query_builder.rb
CHANGED
|
@@ -680,6 +680,140 @@ module CardDB
|
|
|
680
680
|
GRAPHQL
|
|
681
681
|
end
|
|
682
682
|
|
|
683
|
+
def create_scan_job
|
|
684
|
+
<<~GRAPHQL
|
|
685
|
+
mutation CreateScanJob($input: CreateScanJobInput!) {
|
|
686
|
+
createScanJob(input: $input) {
|
|
687
|
+
job {
|
|
688
|
+
#{scan_job_fields}
|
|
689
|
+
}
|
|
690
|
+
created
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
GRAPHQL
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
def scan_job
|
|
697
|
+
<<~GRAPHQL
|
|
698
|
+
query ScanJob($id: UUID!) {
|
|
699
|
+
scanJob(id: $id) {
|
|
700
|
+
#{scan_job_fields}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
GRAPHQL
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def submit_scan_feedback
|
|
707
|
+
<<~GRAPHQL
|
|
708
|
+
mutation SubmitScanFeedback($input: SubmitScanFeedbackInput!) {
|
|
709
|
+
submitScanFeedback(input: $input) {
|
|
710
|
+
feedbackId
|
|
711
|
+
jobId
|
|
712
|
+
status
|
|
713
|
+
persisted
|
|
714
|
+
correct
|
|
715
|
+
predictedRecordId
|
|
716
|
+
selectedRecordId
|
|
717
|
+
featureVersion
|
|
718
|
+
message
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
GRAPHQL
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
def scan_metrics
|
|
725
|
+
<<~GRAPHQL
|
|
726
|
+
query ScanMetrics($input: ScanMetricsInput!) {
|
|
727
|
+
scanMetrics(input: $input) {
|
|
728
|
+
#{scan_metrics_fields}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
GRAPHQL
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
def resolve_scan_template
|
|
735
|
+
<<~GRAPHQL
|
|
736
|
+
query ResolveScanTemplate($input: ResolveScanTemplateInput!) {
|
|
737
|
+
resolveScanTemplate(input: $input) {
|
|
738
|
+
template {
|
|
739
|
+
#{scan_template_fields}
|
|
740
|
+
}
|
|
741
|
+
warnings {
|
|
742
|
+
#{scan_template_warning_fields}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
GRAPHQL
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
def scan_templates
|
|
750
|
+
<<~GRAPHQL
|
|
751
|
+
query ScanTemplates($input: ScanTemplatesInput!) {
|
|
752
|
+
scanTemplates(input: $input) {
|
|
753
|
+
templates {
|
|
754
|
+
#{scan_template_fields}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
GRAPHQL
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
def create_scan_template
|
|
762
|
+
<<~GRAPHQL
|
|
763
|
+
mutation CreateScanTemplate($input: SaveScanTemplateInput!) {
|
|
764
|
+
createScanTemplate(input: $input) {
|
|
765
|
+
template {
|
|
766
|
+
#{scan_template_fields}
|
|
767
|
+
}
|
|
768
|
+
warnings {
|
|
769
|
+
#{scan_template_warning_fields}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
GRAPHQL
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
def update_scan_template
|
|
777
|
+
<<~GRAPHQL
|
|
778
|
+
mutation UpdateScanTemplate($id: UUID!, $input: SaveScanTemplateInput!) {
|
|
779
|
+
updateScanTemplate(id: $id, input: $input) {
|
|
780
|
+
template {
|
|
781
|
+
#{scan_template_fields}
|
|
782
|
+
}
|
|
783
|
+
warnings {
|
|
784
|
+
#{scan_template_warning_fields}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
GRAPHQL
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
def preview_scan_template
|
|
792
|
+
<<~GRAPHQL
|
|
793
|
+
mutation PreviewScanTemplate($input: PreviewScanTemplateInput!) {
|
|
794
|
+
previewScanTemplate(input: $input) {
|
|
795
|
+
warnings {
|
|
796
|
+
#{scan_template_warning_fields}
|
|
797
|
+
}
|
|
798
|
+
regions {
|
|
799
|
+
#{scan_template_preview_region_fields}
|
|
800
|
+
}
|
|
801
|
+
message
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
GRAPHQL
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
def game_scan_regions
|
|
808
|
+
<<~GRAPHQL
|
|
809
|
+
query GameScanRegions($publisherSlug: String!, $gameKey: String!, $includeInactive: Boolean) {
|
|
810
|
+
gameScanRegions(publisherSlug: $publisherSlug, gameKey: $gameKey, includeInactive: $includeInactive) {
|
|
811
|
+
#{game_scan_region_fields}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
GRAPHQL
|
|
815
|
+
end
|
|
816
|
+
|
|
683
817
|
def export_job
|
|
684
818
|
<<~GRAPHQL
|
|
685
819
|
query ExportJob($id: UUID!) {
|
|
@@ -2252,6 +2386,181 @@ module CardDB
|
|
|
2252
2386
|
FIELDS
|
|
2253
2387
|
end
|
|
2254
2388
|
|
|
2389
|
+
def scan_job_fields
|
|
2390
|
+
<<~FIELDS
|
|
2391
|
+
jobId
|
|
2392
|
+
status
|
|
2393
|
+
publisherSlug
|
|
2394
|
+
gameKey
|
|
2395
|
+
datasetKey
|
|
2396
|
+
fileId
|
|
2397
|
+
clientRequestId
|
|
2398
|
+
templateId
|
|
2399
|
+
templateVersionId
|
|
2400
|
+
bestMatch {
|
|
2401
|
+
recordId
|
|
2402
|
+
score
|
|
2403
|
+
confidence
|
|
2404
|
+
}
|
|
2405
|
+
candidates {
|
|
2406
|
+
recordId
|
|
2407
|
+
rank
|
|
2408
|
+
score
|
|
2409
|
+
confidence
|
|
2410
|
+
reasons
|
|
2411
|
+
}
|
|
2412
|
+
extracted
|
|
2413
|
+
metrics {
|
|
2414
|
+
featureVersion
|
|
2415
|
+
candidateCount
|
|
2416
|
+
confidenceScore
|
|
2417
|
+
confidenceLabel
|
|
2418
|
+
stages {
|
|
2419
|
+
name
|
|
2420
|
+
status
|
|
2421
|
+
durationMs
|
|
2422
|
+
completedAt
|
|
2423
|
+
details
|
|
2424
|
+
}
|
|
2425
|
+
timings
|
|
2426
|
+
}
|
|
2427
|
+
webhook {
|
|
2428
|
+
configured
|
|
2429
|
+
url
|
|
2430
|
+
latestDeliveryId
|
|
2431
|
+
status
|
|
2432
|
+
attemptCount
|
|
2433
|
+
nextAttemptAt
|
|
2434
|
+
lastAttemptAt
|
|
2435
|
+
deliveredAt
|
|
2436
|
+
lastStatusCode
|
|
2437
|
+
lastError
|
|
2438
|
+
}
|
|
2439
|
+
error {
|
|
2440
|
+
code
|
|
2441
|
+
message
|
|
2442
|
+
}
|
|
2443
|
+
createdAt
|
|
2444
|
+
updatedAt
|
|
2445
|
+
startedAt
|
|
2446
|
+
completedAt
|
|
2447
|
+
FIELDS
|
|
2448
|
+
end
|
|
2449
|
+
|
|
2450
|
+
def scan_metrics_fields
|
|
2451
|
+
<<~FIELDS
|
|
2452
|
+
publisherSlug
|
|
2453
|
+
gameKey
|
|
2454
|
+
datasetKey
|
|
2455
|
+
since
|
|
2456
|
+
until
|
|
2457
|
+
generatedAt
|
|
2458
|
+
totalJobs
|
|
2459
|
+
completedJobs
|
|
2460
|
+
failedJobs
|
|
2461
|
+
failureRate
|
|
2462
|
+
averageShortlistSize
|
|
2463
|
+
confidenceDistribution
|
|
2464
|
+
feedback {
|
|
2465
|
+
total
|
|
2466
|
+
correct
|
|
2467
|
+
corrected
|
|
2468
|
+
unknown
|
|
2469
|
+
accuracy
|
|
2470
|
+
}
|
|
2471
|
+
featureVersions {
|
|
2472
|
+
featureVersion
|
|
2473
|
+
jobCount
|
|
2474
|
+
completedJobs
|
|
2475
|
+
failedJobs
|
|
2476
|
+
averageConfidence
|
|
2477
|
+
averageShortlistSize
|
|
2478
|
+
feedbackCount
|
|
2479
|
+
correctFeedback
|
|
2480
|
+
correctedFeedback
|
|
2481
|
+
feedbackAccuracy
|
|
2482
|
+
}
|
|
2483
|
+
FIELDS
|
|
2484
|
+
end
|
|
2485
|
+
|
|
2486
|
+
def scan_template_warning_fields
|
|
2487
|
+
<<~FIELDS
|
|
2488
|
+
code
|
|
2489
|
+
region
|
|
2490
|
+
message
|
|
2491
|
+
FIELDS
|
|
2492
|
+
end
|
|
2493
|
+
|
|
2494
|
+
def scan_template_region_fields
|
|
2495
|
+
<<~FIELDS
|
|
2496
|
+
id
|
|
2497
|
+
key
|
|
2498
|
+
label
|
|
2499
|
+
sortOrder
|
|
2500
|
+
shapeType
|
|
2501
|
+
geometry
|
|
2502
|
+
semanticType
|
|
2503
|
+
extractionMode
|
|
2504
|
+
lookupMode
|
|
2505
|
+
isRequired
|
|
2506
|
+
weight
|
|
2507
|
+
config
|
|
2508
|
+
FIELDS
|
|
2509
|
+
end
|
|
2510
|
+
|
|
2511
|
+
def scan_template_fields
|
|
2512
|
+
<<~FIELDS
|
|
2513
|
+
id
|
|
2514
|
+
key
|
|
2515
|
+
name
|
|
2516
|
+
status
|
|
2517
|
+
version
|
|
2518
|
+
isDefault
|
|
2519
|
+
publisherSlug
|
|
2520
|
+
gameKey
|
|
2521
|
+
datasetKey
|
|
2522
|
+
exampleFileId
|
|
2523
|
+
coordinateSpace
|
|
2524
|
+
cardAspectRatio
|
|
2525
|
+
notes
|
|
2526
|
+
config
|
|
2527
|
+
regions {
|
|
2528
|
+
#{scan_template_region_fields}
|
|
2529
|
+
}
|
|
2530
|
+
createdAt
|
|
2531
|
+
updatedAt
|
|
2532
|
+
FIELDS
|
|
2533
|
+
end
|
|
2534
|
+
|
|
2535
|
+
def scan_template_preview_region_fields
|
|
2536
|
+
<<~FIELDS
|
|
2537
|
+
key
|
|
2538
|
+
label
|
|
2539
|
+
semanticType
|
|
2540
|
+
extractionMode
|
|
2541
|
+
lookupMode
|
|
2542
|
+
rawText
|
|
2543
|
+
normalizedValue
|
|
2544
|
+
source
|
|
2545
|
+
status
|
|
2546
|
+
confidence
|
|
2547
|
+
FIELDS
|
|
2548
|
+
end
|
|
2549
|
+
|
|
2550
|
+
def game_scan_region_fields
|
|
2551
|
+
<<~FIELDS
|
|
2552
|
+
id
|
|
2553
|
+
key
|
|
2554
|
+
name
|
|
2555
|
+
description
|
|
2556
|
+
sortOrder
|
|
2557
|
+
isActive
|
|
2558
|
+
metadata
|
|
2559
|
+
createdAt
|
|
2560
|
+
updatedAt
|
|
2561
|
+
FIELDS
|
|
2562
|
+
end
|
|
2563
|
+
|
|
2255
2564
|
def import_job_connection_fields
|
|
2256
2565
|
connection_fields(import_job_fields)
|
|
2257
2566
|
end
|
|
@@ -29,6 +29,39 @@ module CardDB
|
|
|
29
29
|
File.new(data['fileUploadConfirm'], client: client)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# Request a presigned URL, upload bytes directly to storage, and confirm the file.
|
|
33
|
+
def upload(
|
|
34
|
+
filename:,
|
|
35
|
+
content_type:,
|
|
36
|
+
body:,
|
|
37
|
+
size: nil,
|
|
38
|
+
purpose: nil,
|
|
39
|
+
is_public: false,
|
|
40
|
+
entity_type: nil,
|
|
41
|
+
entity_id: nil,
|
|
42
|
+
publisher_id: nil,
|
|
43
|
+
dataset_id: nil
|
|
44
|
+
)
|
|
45
|
+
upload_size = size || infer_upload_size(body)
|
|
46
|
+
raise ArgumentError, 'files.upload requires a positive size or an inferable body size' unless upload_size&.positive?
|
|
47
|
+
|
|
48
|
+
upload = request_upload(
|
|
49
|
+
input: {
|
|
50
|
+
filename: filename,
|
|
51
|
+
contentType: content_type,
|
|
52
|
+
size: upload_size,
|
|
53
|
+
isPublic: is_public,
|
|
54
|
+
entityType: entity_type || entity_type_for_purpose(purpose),
|
|
55
|
+
entityId: entity_id,
|
|
56
|
+
publisherId: publisher_id,
|
|
57
|
+
datasetId: dataset_id
|
|
58
|
+
}.compact
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
put_upload(upload.upload_url, content_type, body)
|
|
62
|
+
confirm_upload(id: upload.file.id)
|
|
63
|
+
end
|
|
64
|
+
|
|
32
65
|
# Delete a file. Requires a server-side secret credential.
|
|
33
66
|
# rubocop:disable Naming/PredicateMethod
|
|
34
67
|
def delete(id:)
|
|
@@ -38,6 +71,48 @@ module CardDB
|
|
|
38
71
|
!!data['fileDelete']
|
|
39
72
|
end
|
|
40
73
|
# rubocop:enable Naming/PredicateMethod
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def put_upload(upload_url, content_type, body)
|
|
78
|
+
response = Faraday.put(upload_url) do |request|
|
|
79
|
+
request.headers['Content-Type'] = content_type
|
|
80
|
+
request.body = body
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
return if response.success?
|
|
84
|
+
|
|
85
|
+
raise Error, "file upload failed with #{response.status}: #{response.body}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def infer_upload_size(body)
|
|
89
|
+
return body.bytesize if body.is_a?(String)
|
|
90
|
+
return body.bytesize if body.respond_to?(:bytesize)
|
|
91
|
+
return body.size if body.respond_to?(:size) && body.size.is_a?(Integer)
|
|
92
|
+
return body.stat.size if body.respond_to?(:stat)
|
|
93
|
+
|
|
94
|
+
infer_io_size(body)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def infer_io_size(body)
|
|
98
|
+
return nil unless body.respond_to?(:tell) && body.respond_to?(:seek)
|
|
99
|
+
|
|
100
|
+
original_position = body.tell
|
|
101
|
+
body.seek(0, IO::SEEK_END)
|
|
102
|
+
size = body.tell
|
|
103
|
+
body.seek(original_position, IO::SEEK_SET)
|
|
104
|
+
size
|
|
105
|
+
rescue IOError, SystemCallError
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def entity_type_for_purpose(purpose)
|
|
110
|
+
return nil if purpose.nil? || purpose.to_s == 'scan_image'
|
|
111
|
+
return 'import' if purpose.to_s == 'import'
|
|
112
|
+
return 'scan_template_example' if purpose.to_s == 'scan_template_example'
|
|
113
|
+
|
|
114
|
+
purpose.to_s
|
|
115
|
+
end
|
|
41
116
|
end
|
|
42
117
|
end
|
|
43
118
|
end
|
|
@@ -74,6 +74,25 @@ module CardDB
|
|
|
74
74
|
Game.new(data['gameUpdate'], client: client)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
# Get publisher-defined game region metadata for scan-capable clients.
|
|
78
|
+
def get_regions(publisher_slug: nil, game_key: nil, include_inactive: nil, cache: nil)
|
|
79
|
+
resolved_publisher = resolve_publisher(publisher_slug)
|
|
80
|
+
resolved_game = resolve_game(game_key)
|
|
81
|
+
validate_access!(resolved_publisher, resolved_game)
|
|
82
|
+
|
|
83
|
+
variables = build_variables(
|
|
84
|
+
publisherSlug: resolved_publisher,
|
|
85
|
+
gameKey: resolved_game,
|
|
86
|
+
includeInactive: include_inactive
|
|
87
|
+
)
|
|
88
|
+
key = cache_key('games', 'get_regions', **variables)
|
|
89
|
+
|
|
90
|
+
with_cache(key, resource: :games, cache: cache) do
|
|
91
|
+
data = connection.execute(QueryBuilder.game_scan_regions, variables)
|
|
92
|
+
(data['gameScanRegions'] || []).map { |region| GameScanRegion.new(region, client: client) }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
77
96
|
# Search for games
|
|
78
97
|
#
|
|
79
98
|
# @param publisher_slug [String, nil] Filter by publisher slug
|
|
@@ -18,9 +18,9 @@ module CardDB
|
|
|
18
18
|
before: nil,
|
|
19
19
|
validate_schema: nil,
|
|
20
20
|
cache: nil,
|
|
21
|
-
&
|
|
21
|
+
&
|
|
22
22
|
)
|
|
23
|
-
final_filter = block_given? ? FilterBuilder.build(&
|
|
23
|
+
final_filter = block_given? ? FilterBuilder.build(&) : filter
|
|
24
24
|
query = QueryBuilder.list_dataset_records(
|
|
25
25
|
dataset_id: dataset_id,
|
|
26
26
|
filter: final_filter,
|
|
@@ -206,7 +206,7 @@ module CardDB
|
|
|
206
206
|
after: nil,
|
|
207
207
|
validate_schema: nil,
|
|
208
208
|
include_pricing: false,
|
|
209
|
-
&
|
|
209
|
+
&
|
|
210
210
|
)
|
|
211
211
|
resolved_publisher = resolve_publisher(publisher_slug)
|
|
212
212
|
resolved_game = resolve_game(game_key)
|
|
@@ -215,7 +215,7 @@ module CardDB
|
|
|
215
215
|
|
|
216
216
|
# Build filter from DSL block if provided
|
|
217
217
|
final_filter = if block_given?
|
|
218
|
-
FilterBuilder.build(&
|
|
218
|
+
FilterBuilder.build(&)
|
|
219
219
|
else
|
|
220
220
|
filter
|
|
221
221
|
end
|
|
@@ -27,22 +27,22 @@ module CardDB
|
|
|
27
27
|
#
|
|
28
28
|
# @param dataset_key [String] Rules dataset key (defaults to "rules")
|
|
29
29
|
# @return [Collection<Record>] Collection of rule records
|
|
30
|
-
def list(dataset_key: 'rules',
|
|
31
|
-
search_records(dataset_key: dataset_key,
|
|
30
|
+
def list(dataset_key: 'rules', **, &)
|
|
31
|
+
search_records(dataset_key: dataset_key, **, &)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# List deck/game formats from the game's formats dataset.
|
|
35
35
|
#
|
|
36
36
|
# @param dataset_key [String] Formats dataset key (defaults to "formats")
|
|
37
37
|
# @return [Collection<Record>] Collection of format records
|
|
38
|
-
def formats(dataset_key: 'formats',
|
|
39
|
-
search_records(dataset_key: dataset_key,
|
|
38
|
+
def formats(dataset_key: 'formats', **, &)
|
|
39
|
+
search_records(dataset_key: dataset_key, **, &)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
private
|
|
43
43
|
|
|
44
|
-
def search_records(dataset_key:,
|
|
45
|
-
client.records.search(dataset_key: dataset_key,
|
|
44
|
+
def search_records(dataset_key:, **, &)
|
|
45
|
+
client.records.search(dataset_key: dataset_key, **, &)
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CardDB
|
|
4
|
+
module Resources
|
|
5
|
+
# Scan template resource for publisher-managed scan layouts.
|
|
6
|
+
class ScanTemplates < Base
|
|
7
|
+
SCOPE_KEY_MAP = {
|
|
8
|
+
publisher_slug: 'publisherSlug',
|
|
9
|
+
game_key: 'gameKey',
|
|
10
|
+
dataset_key: 'datasetKey',
|
|
11
|
+
template_id: 'templateId',
|
|
12
|
+
include_archived: 'includeArchived'
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
TEMPLATE_KEY_MAP = SCOPE_KEY_MAP.merge(
|
|
16
|
+
is_default: 'isDefault',
|
|
17
|
+
example_file_id: 'exampleFileId',
|
|
18
|
+
card_aspect_ratio: 'cardAspectRatio',
|
|
19
|
+
region_ocr_hints: 'regionOcrHints'
|
|
20
|
+
).freeze
|
|
21
|
+
|
|
22
|
+
REGION_KEY_MAP = {
|
|
23
|
+
sort_order: 'sortOrder',
|
|
24
|
+
shape_type: 'shapeType',
|
|
25
|
+
semantic_type: 'semanticType',
|
|
26
|
+
extraction_mode: 'extractionMode',
|
|
27
|
+
lookup_mode: 'lookupMode',
|
|
28
|
+
is_required: 'isRequired'
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
# Resolve the active scan template for a game or dataset scope.
|
|
32
|
+
def resolve(input = nil, **params)
|
|
33
|
+
request = scoped_input(graphql_input(coerce_input(input, params), SCOPE_KEY_MAP))
|
|
34
|
+
key = cache_key('scan_templates', 'resolve', **request)
|
|
35
|
+
|
|
36
|
+
with_cache(key, resource: :scan_templates, cache: params[:cache]) do
|
|
37
|
+
data = connection.execute(QueryBuilder.resolve_scan_template, { input: request })
|
|
38
|
+
ScanTemplateResolution.new(data['resolveScanTemplate'], client: client)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# List publisher-managed scan templates for a game or dataset scope.
|
|
43
|
+
def list(input = nil, **params)
|
|
44
|
+
request = scoped_input(graphql_input(coerce_input(input, params), SCOPE_KEY_MAP))
|
|
45
|
+
key = cache_key('scan_templates', 'list', **request)
|
|
46
|
+
|
|
47
|
+
with_cache(key, resource: :scan_templates, cache: params[:cache]) do
|
|
48
|
+
data = connection.execute(QueryBuilder.scan_templates, { input: request })
|
|
49
|
+
(data.dig('scanTemplates', 'templates') || []).map do |template|
|
|
50
|
+
ScanTemplate.new(template, client: client)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Create a publisher-managed scan template. Requires a server-side secret credential.
|
|
56
|
+
def create(input = nil, **params)
|
|
57
|
+
config.require_secret_credential!('scan_templates.create')
|
|
58
|
+
|
|
59
|
+
request = template_input(coerce_input(input, params))
|
|
60
|
+
data = connection.execute(QueryBuilder.create_scan_template, { input: request })
|
|
61
|
+
|
|
62
|
+
ScanTemplatePayload.new(data['createScanTemplate'], client: client)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Replace a publisher-managed scan template. Requires a server-side secret credential.
|
|
66
|
+
def update(id = nil, input = nil, **params)
|
|
67
|
+
config.require_secret_credential!('scan_templates.update')
|
|
68
|
+
|
|
69
|
+
id, request_source = update_args(id, input, params)
|
|
70
|
+
raise ArgumentError, 'scan template id is required' unless id
|
|
71
|
+
|
|
72
|
+
request = template_input(request_source)
|
|
73
|
+
data = connection.execute(QueryBuilder.update_scan_template, { id: id, input: request })
|
|
74
|
+
|
|
75
|
+
ScanTemplatePayload.new(data['updateScanTemplate'], client: client)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Validate a draft scan template and normalize provided region OCR hints.
|
|
79
|
+
def preview(input = nil, **params)
|
|
80
|
+
request = template_input(coerce_input(input, params))
|
|
81
|
+
data = connection.execute(QueryBuilder.preview_scan_template, { input: request })
|
|
82
|
+
|
|
83
|
+
ScanTemplatePreview.new(data['previewScanTemplate'], client: client)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def update_args(id, input, params)
|
|
89
|
+
if id.is_a?(Hash) && input.nil?
|
|
90
|
+
source = id.dup
|
|
91
|
+
template_id = source.delete(:id) || source.delete('id')
|
|
92
|
+
return [template_id, source]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
source = coerce_input(input, params)
|
|
96
|
+
[id || params[:id] || params['id'], source]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def template_input(input)
|
|
100
|
+
request = scoped_input(graphql_input(input, TEMPLATE_KEY_MAP))
|
|
101
|
+
request['regions'] = Array(request['regions']).map { |region| graphql_input(region, REGION_KEY_MAP) }
|
|
102
|
+
request
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def scoped_input(input)
|
|
106
|
+
publisher_slug = input['publisherSlug'] || resolve_publisher(nil)
|
|
107
|
+
game_key = input['gameKey'] || resolve_game(nil)
|
|
108
|
+
validate_access!(publisher_slug, game_key)
|
|
109
|
+
|
|
110
|
+
input.merge('publisherSlug' => publisher_slug, 'gameKey' => game_key)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def coerce_input(input, params)
|
|
114
|
+
params = params.dup
|
|
115
|
+
input = params.delete(:input) || params.delete('input') if input.nil?
|
|
116
|
+
(input || {}).merge(params.reject { |key, _value| %i[cache id].include?(key) || %w[cache id].include?(key) })
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def graphql_input(input, key_map)
|
|
120
|
+
input.each_with_object({}) do |(key, value), output|
|
|
121
|
+
next if value.nil?
|
|
122
|
+
|
|
123
|
+
output[key_map[key.to_sym] || key.to_s] = value
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|