carddb 0.2.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 +7 -0
- data/.rspec +3 -0
- data/.rspec_status +96 -0
- data/.rubocop.yml +72 -0
- data/AGENTS.md +27 -0
- data/CHANGELOG.md +127 -0
- data/LICENSE.txt +21 -0
- data/README.md +732 -0
- data/Rakefile +10 -0
- data/examples/basic_usage.rb +60 -0
- data/examples/filtering.rb +93 -0
- data/examples/pagination.rb +58 -0
- data/lib/carddb/batch.rb +287 -0
- data/lib/carddb/cache.rb +120 -0
- data/lib/carddb/client.rb +139 -0
- data/lib/carddb/collection.rb +919 -0
- data/lib/carddb/configuration.rb +185 -0
- data/lib/carddb/connection.rb +224 -0
- data/lib/carddb/errors.rb +85 -0
- data/lib/carddb/filter_builder.rb +214 -0
- data/lib/carddb/query_builder.rb +658 -0
- data/lib/carddb/resources/base.rb +86 -0
- data/lib/carddb/resources/datasets.rb +132 -0
- data/lib/carddb/resources/decks.rb +125 -0
- data/lib/carddb/resources/games.rb +111 -0
- data/lib/carddb/resources/publishers.rb +86 -0
- data/lib/carddb/resources/records.rb +239 -0
- data/lib/carddb/resources/rules.rb +49 -0
- data/lib/carddb/version.rb +5 -0
- data/lib/carddb.rb +160 -0
- metadata +102 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CardDB
|
|
4
|
+
module Resources
|
|
5
|
+
# Base class for API resources
|
|
6
|
+
class Base
|
|
7
|
+
# @return [Client] The client instance
|
|
8
|
+
attr_reader :client
|
|
9
|
+
|
|
10
|
+
# @return [Connection] The API connection
|
|
11
|
+
attr_reader :connection
|
|
12
|
+
|
|
13
|
+
# @return [Configuration] The configuration
|
|
14
|
+
attr_reader :config
|
|
15
|
+
|
|
16
|
+
# @param client [Client] The client instance
|
|
17
|
+
# @param connection [Connection] The API connection
|
|
18
|
+
# @param config [Configuration] The configuration
|
|
19
|
+
def initialize(client, connection, config)
|
|
20
|
+
@client = client
|
|
21
|
+
@connection = connection
|
|
22
|
+
@config = config
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# Resolve publisher slug, using default if not provided
|
|
28
|
+
def resolve_publisher(publisher_slug)
|
|
29
|
+
resolved = config.resolve_publisher(publisher_slug)
|
|
30
|
+
return resolved if resolved
|
|
31
|
+
|
|
32
|
+
raise ArgumentError, 'publisher_slug is required (no default configured)'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Resolve game key, using default if not provided
|
|
36
|
+
def resolve_game(game_key)
|
|
37
|
+
resolved = config.resolve_game(game_key)
|
|
38
|
+
return resolved if resolved
|
|
39
|
+
|
|
40
|
+
raise ArgumentError, 'game_key is required (no default configured)'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Validate access to publisher/game
|
|
44
|
+
def validate_access!(publisher_slug, game_key = nil)
|
|
45
|
+
config.validate_access!(publisher_slug, game_key)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Build variables hash, removing nil values
|
|
49
|
+
def build_variables(**vars)
|
|
50
|
+
vars.compact
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Execute a query with optional caching.
|
|
54
|
+
#
|
|
55
|
+
# @param cache_key [String] The cache key
|
|
56
|
+
# @param resource [Symbol] The resource type for TTL lookup (:publishers, :games, :datasets, :records)
|
|
57
|
+
# @param cache [Boolean, nil] Whether to use cache (nil = use config default)
|
|
58
|
+
# @yield Block that executes the query and returns the result
|
|
59
|
+
# @return [Object] The result (from cache or fresh)
|
|
60
|
+
def with_cache(cache_key, resource:, cache: nil)
|
|
61
|
+
use_cache = cache.nil? ? config.cache : cache
|
|
62
|
+
return yield unless use_cache && config.cache
|
|
63
|
+
|
|
64
|
+
# Try to read from cache
|
|
65
|
+
cached = config.cache.read(cache_key)
|
|
66
|
+
return cached if cached
|
|
67
|
+
|
|
68
|
+
# Execute query and cache result with resource-specific TTL
|
|
69
|
+
result = yield
|
|
70
|
+
ttl = config.cache_ttl_for(resource)
|
|
71
|
+
config.cache.write(cache_key, result, expires_in: ttl) if result
|
|
72
|
+
result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Generate a cache key for a resource operation.
|
|
76
|
+
#
|
|
77
|
+
# @param resource [String] Resource type
|
|
78
|
+
# @param method [String] Method name
|
|
79
|
+
# @param params [Hash] Parameters
|
|
80
|
+
# @return [String] The cache key
|
|
81
|
+
def cache_key(resource, method, **params)
|
|
82
|
+
CacheSupport.cache_key(resource, method, params)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CardDB
|
|
4
|
+
module Resources
|
|
5
|
+
# Datasets resource for searching, fetching, and getting schema
|
|
6
|
+
class Datasets < Base
|
|
7
|
+
# Search for datasets
|
|
8
|
+
#
|
|
9
|
+
# @param publisher_slug [String, nil] Filter by publisher slug
|
|
10
|
+
# @param game_key [String, nil] Filter by game key
|
|
11
|
+
# @param search [String, nil] Search by name
|
|
12
|
+
# @param purpose [String, nil] Filter by dataset purpose (DATA or RULES)
|
|
13
|
+
# @param first [Integer, nil] Maximum number of results
|
|
14
|
+
# @param after [String, nil] Cursor for pagination
|
|
15
|
+
# @return [Collection<Dataset>] Collection of datasets
|
|
16
|
+
def search(publisher_slug: nil, game_key: nil, search: nil, purpose: nil, first: nil, after: nil)
|
|
17
|
+
resolved_publisher = config.resolve_publisher(publisher_slug)
|
|
18
|
+
resolved_game = config.resolve_game(game_key)
|
|
19
|
+
|
|
20
|
+
validate_access!(resolved_publisher, resolved_game) if resolved_publisher
|
|
21
|
+
|
|
22
|
+
query = QueryBuilder.search_datasets(
|
|
23
|
+
publisher_slug: resolved_publisher,
|
|
24
|
+
game_key: resolved_game,
|
|
25
|
+
search: search,
|
|
26
|
+
purpose: purpose,
|
|
27
|
+
first: first,
|
|
28
|
+
after: after
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
variables = build_variables(
|
|
32
|
+
publisherSlug: resolved_publisher,
|
|
33
|
+
gameKey: resolved_game,
|
|
34
|
+
search: search,
|
|
35
|
+
purpose: purpose,
|
|
36
|
+
first: first,
|
|
37
|
+
after: after
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
data = connection.execute(query, variables)
|
|
41
|
+
|
|
42
|
+
# Create next page loader
|
|
43
|
+
search_params = { publisher_slug: publisher_slug, game_key: game_key, search: search, purpose: purpose, first: first }
|
|
44
|
+
next_page_loader = ->(cursor) { search(**search_params, after: cursor) }
|
|
45
|
+
|
|
46
|
+
Collection.new(
|
|
47
|
+
data['searchDatasets'],
|
|
48
|
+
item_class: Dataset,
|
|
49
|
+
next_page_loader: next_page_loader,
|
|
50
|
+
client: client
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Fetch a dataset by ID (includes schema)
|
|
55
|
+
#
|
|
56
|
+
# @param id [String] The dataset UUID
|
|
57
|
+
# @param cache [Boolean, nil] Whether to cache (nil = use config setting)
|
|
58
|
+
# @return [Dataset, nil] The dataset or nil if not found
|
|
59
|
+
def fetch(id, cache: nil)
|
|
60
|
+
key = cache_key('datasets', 'fetch', id: id)
|
|
61
|
+
with_cache(key, resource: :datasets, cache: cache) do
|
|
62
|
+
query = QueryBuilder.fetch_dataset_by_id
|
|
63
|
+
data = connection.execute(query, { id: id })
|
|
64
|
+
|
|
65
|
+
return nil unless data['fetchDataset']
|
|
66
|
+
|
|
67
|
+
Dataset.new(data['fetchDataset'], client: client)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Get a dataset by publisher/game/dataset keys (includes schema)
|
|
72
|
+
#
|
|
73
|
+
# @param publisher_slug [String, nil] Publisher slug (uses default if not provided)
|
|
74
|
+
# @param game_key [String, nil] Game key (uses default if not provided)
|
|
75
|
+
# @param dataset_key [String] Dataset key (required)
|
|
76
|
+
# @param cache [Boolean, nil] Whether to cache (nil = use config setting)
|
|
77
|
+
# @return [Dataset, nil] The dataset or nil if not found
|
|
78
|
+
def get(dataset_key:, publisher_slug: nil, game_key: nil, cache: nil)
|
|
79
|
+
resolved_publisher = resolve_publisher(publisher_slug)
|
|
80
|
+
resolved_game = resolve_game(game_key)
|
|
81
|
+
|
|
82
|
+
validate_access!(resolved_publisher, resolved_game)
|
|
83
|
+
|
|
84
|
+
key = cache_key('datasets', 'get', publisher_slug: resolved_publisher, game_key: resolved_game,
|
|
85
|
+
dataset_key: dataset_key)
|
|
86
|
+
with_cache(key, resource: :datasets, cache: cache) do
|
|
87
|
+
query = QueryBuilder.fetch_dataset_by_keys
|
|
88
|
+
variables = {
|
|
89
|
+
publisherSlug: resolved_publisher,
|
|
90
|
+
gameKey: resolved_game,
|
|
91
|
+
datasetKey: dataset_key
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
data = connection.execute(query, variables)
|
|
95
|
+
|
|
96
|
+
return nil unless data['fetchDataset']
|
|
97
|
+
|
|
98
|
+
Dataset.new(data['fetchDataset'], client: client)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Fetch multiple datasets by IDs
|
|
103
|
+
#
|
|
104
|
+
# @param ids [Array<String>] Array of dataset UUIDs (max 100)
|
|
105
|
+
# @return [Array<Dataset>] Array of datasets
|
|
106
|
+
# @raise [ArgumentError] If more than 100 IDs provided
|
|
107
|
+
def fetch_many(ids)
|
|
108
|
+
raise ArgumentError, 'Maximum 100 IDs allowed' if ids.length > 100
|
|
109
|
+
|
|
110
|
+
query = QueryBuilder.fetch_datasets
|
|
111
|
+
data = connection.execute(query, { ids: ids })
|
|
112
|
+
|
|
113
|
+
(data['fetchDatasets'] || []).map { |d| Dataset.new(d, client: client) }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Get the schema for a dataset
|
|
117
|
+
#
|
|
118
|
+
# @param dataset_key [String] Dataset key
|
|
119
|
+
# @param publisher_slug [String, nil] Publisher slug (uses default if not provided)
|
|
120
|
+
# @param game_key [String, nil] Game key (uses default if not provided)
|
|
121
|
+
# @return [DatasetSchema, nil] The schema or nil if not found
|
|
122
|
+
def schema(dataset_key:, publisher_slug: nil, game_key: nil)
|
|
123
|
+
dataset = get(
|
|
124
|
+
dataset_key: dataset_key,
|
|
125
|
+
publisher_slug: publisher_slug,
|
|
126
|
+
game_key: game_key
|
|
127
|
+
)
|
|
128
|
+
dataset&.schema
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CardDB
|
|
4
|
+
module Resources
|
|
5
|
+
# Decks resource for hosted decks and external deck hydration.
|
|
6
|
+
class Decks < Base
|
|
7
|
+
# List decks owned by the current account or API application.
|
|
8
|
+
def list_mine(first: nil, after: nil, cache: nil)
|
|
9
|
+
query = QueryBuilder.list_my_decks(first: first, after: after)
|
|
10
|
+
variables = build_variables(first: first, after: after)
|
|
11
|
+
key = cache_key('decks', 'list_mine', **variables)
|
|
12
|
+
|
|
13
|
+
data = with_cache(key, resource: :decks, cache: cache) do
|
|
14
|
+
connection.execute(query, variables)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
Collection.new(
|
|
18
|
+
data['myDecks'],
|
|
19
|
+
item_class: Deck,
|
|
20
|
+
next_page_loader: ->(cursor) { list_mine(first: first, after: cursor, cache: cache) },
|
|
21
|
+
client: client
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Fetch a hosted deck by CardDB UUID.
|
|
26
|
+
def fetch(id, cache: nil)
|
|
27
|
+
key = cache_key('decks', 'fetch', id: id)
|
|
28
|
+
with_cache(key, resource: :decks, cache: cache) do
|
|
29
|
+
data = connection.execute(QueryBuilder.fetch_deck, { id: id })
|
|
30
|
+
data['fetchDeck'] ? Deck.new(data['fetchDeck'], client: client) : nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Fetch a hosted deck by external reference for the current API application.
|
|
35
|
+
def fetch_by_external_ref(external_ref:, cache: nil)
|
|
36
|
+
key = cache_key('decks', 'fetch_by_external_ref', external_ref: external_ref)
|
|
37
|
+
with_cache(key, resource: :decks, cache: cache) do
|
|
38
|
+
data = connection.execute(QueryBuilder.fetch_deck_by_external_ref, { externalRef: external_ref })
|
|
39
|
+
data['fetchDeckByExternalRef'] ? Deck.new(data['fetchDeckByExternalRef'], client: client) : nil
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Create a hosted deck.
|
|
44
|
+
def create(input:)
|
|
45
|
+
data = connection.execute(QueryBuilder.create_deck, { input: input })
|
|
46
|
+
Deck.new(data['deckCreate'], client: client)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Update a hosted deck.
|
|
50
|
+
def update(id:, input:)
|
|
51
|
+
data = connection.execute(QueryBuilder.update_deck, { id: id, input: input })
|
|
52
|
+
Deck.new(data['deckUpdate'], client: client)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Delete a hosted deck.
|
|
56
|
+
# rubocop:disable Naming/PredicateMethod
|
|
57
|
+
def delete(id:)
|
|
58
|
+
data = connection.execute(QueryBuilder.delete_deck, { id: id })
|
|
59
|
+
!!data['deckDelete']
|
|
60
|
+
end
|
|
61
|
+
# rubocop:enable Naming/PredicateMethod
|
|
62
|
+
|
|
63
|
+
# Hydrate third-party-owned deck entries without storing them in CardDB.
|
|
64
|
+
def hydrate_entries(dataset_key:, entries:, publisher_slug: nil, game_key: nil, identifier_field: nil, cache: nil)
|
|
65
|
+
return [] if entries.empty?
|
|
66
|
+
|
|
67
|
+
resolved_publisher = resolve_publisher(publisher_slug)
|
|
68
|
+
resolved_game = resolve_game(game_key)
|
|
69
|
+
validate_access!(resolved_publisher, resolved_game)
|
|
70
|
+
|
|
71
|
+
identifiers = entries.map { |entry| entry_identifier(entry) }
|
|
72
|
+
key = cache_key(
|
|
73
|
+
'decks',
|
|
74
|
+
'hydrate_entries',
|
|
75
|
+
publisher_slug: resolved_publisher,
|
|
76
|
+
game_key: resolved_game,
|
|
77
|
+
dataset_key: dataset_key,
|
|
78
|
+
identifiers: identifiers
|
|
79
|
+
)
|
|
80
|
+
records = with_cache(key, resource: :decks, cache: cache) do
|
|
81
|
+
data = connection.execute(
|
|
82
|
+
QueryBuilder.fetch_records_by_identifier,
|
|
83
|
+
{
|
|
84
|
+
publisherSlug: resolved_publisher,
|
|
85
|
+
gameKey: resolved_game,
|
|
86
|
+
datasetKey: dataset_key,
|
|
87
|
+
identifiers: identifiers
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
(data['fetchRecordsByIdentifier'] || []).map { |record| Record.new(record, client: client) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
records_by_identifier = records_by_deck_identifier(records, identifiers, identifier_field)
|
|
94
|
+
entries.map do |entry|
|
|
95
|
+
entry.merge(record: records_by_identifier[entry_identifier(entry)])
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def entry_identifier(entry)
|
|
102
|
+
entry[:identifier] || entry['identifier'] || raise(ArgumentError, 'entry identifier is required')
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def records_by_deck_identifier(records, identifiers, identifier_field)
|
|
106
|
+
remaining = identifiers.to_h { |identifier| [identifier.to_s, true] }
|
|
107
|
+
records.each_with_object({}) do |record, hash|
|
|
108
|
+
identifier = deck_record_identifier(record, remaining, identifier_field)
|
|
109
|
+
next unless identifier
|
|
110
|
+
|
|
111
|
+
key = identifier.to_s
|
|
112
|
+
next unless remaining.delete(key)
|
|
113
|
+
|
|
114
|
+
hash[key] = record
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def deck_record_identifier(record, remaining, identifier_field)
|
|
119
|
+
return record[identifier_field] if identifier_field
|
|
120
|
+
|
|
121
|
+
record.record_data&.values&.find { |value| remaining[value.to_s] }
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CardDB
|
|
4
|
+
module Resources
|
|
5
|
+
# Games resource for searching and fetching games
|
|
6
|
+
class Games < Base
|
|
7
|
+
# Search for games
|
|
8
|
+
#
|
|
9
|
+
# @param publisher_slug [String, nil] Filter by publisher slug
|
|
10
|
+
# @param search [String, nil] Search by name
|
|
11
|
+
# @param first [Integer, nil] Maximum number of results
|
|
12
|
+
# @param after [String, nil] Cursor for pagination
|
|
13
|
+
# @return [Collection<Game>] Collection of games
|
|
14
|
+
def search(publisher_slug: nil, search: nil, first: nil, after: nil)
|
|
15
|
+
resolved_publisher = config.resolve_publisher(publisher_slug)
|
|
16
|
+
validate_access!(resolved_publisher, nil) if resolved_publisher
|
|
17
|
+
|
|
18
|
+
query = QueryBuilder.search_games(
|
|
19
|
+
publisher_slug: resolved_publisher,
|
|
20
|
+
search: search,
|
|
21
|
+
first: first,
|
|
22
|
+
after: after
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
variables = build_variables(
|
|
26
|
+
publisherSlug: resolved_publisher,
|
|
27
|
+
search: search,
|
|
28
|
+
first: first,
|
|
29
|
+
after: after
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
data = connection.execute(query, variables)
|
|
33
|
+
|
|
34
|
+
# Create next page loader
|
|
35
|
+
next_page_loader = lambda do |cursor|
|
|
36
|
+
search(
|
|
37
|
+
publisher_slug: publisher_slug,
|
|
38
|
+
search: search,
|
|
39
|
+
first: first,
|
|
40
|
+
after: cursor
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
Collection.new(
|
|
45
|
+
data['searchGames'],
|
|
46
|
+
item_class: Game,
|
|
47
|
+
next_page_loader: next_page_loader,
|
|
48
|
+
client: client
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Fetch a game by ID
|
|
53
|
+
#
|
|
54
|
+
# @param id [String] The game UUID
|
|
55
|
+
# @param cache [Boolean, nil] Whether to cache (nil = use config setting)
|
|
56
|
+
# @return [Game, nil] The game or nil if not found
|
|
57
|
+
def fetch(id, cache: nil)
|
|
58
|
+
key = cache_key('games', 'fetch', id: id)
|
|
59
|
+
with_cache(key, resource: :games, cache: cache) do
|
|
60
|
+
query = QueryBuilder.fetch_game_by_id
|
|
61
|
+
data = connection.execute(query, { id: id })
|
|
62
|
+
|
|
63
|
+
return nil unless data['fetchGame']
|
|
64
|
+
|
|
65
|
+
Game.new(data['fetchGame'], client: client)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Get a game by publisher slug and game key
|
|
70
|
+
#
|
|
71
|
+
# @param game_key [String] Game key (required)
|
|
72
|
+
# @param publisher_slug [String, nil] Publisher slug (uses default if not provided)
|
|
73
|
+
# @param cache [Boolean, nil] Whether to cache (nil = use config setting)
|
|
74
|
+
# @return [Game, nil] The game or nil if not found
|
|
75
|
+
def get(game_key:, publisher_slug: nil, cache: nil)
|
|
76
|
+
resolved_publisher = resolve_publisher(publisher_slug)
|
|
77
|
+
|
|
78
|
+
validate_access!(resolved_publisher, game_key)
|
|
79
|
+
|
|
80
|
+
key = cache_key('games', 'get', publisher_slug: resolved_publisher, game_key: game_key)
|
|
81
|
+
with_cache(key, resource: :games, cache: cache) do
|
|
82
|
+
query = QueryBuilder.fetch_game_by_keys
|
|
83
|
+
variables = {
|
|
84
|
+
publisherSlug: resolved_publisher,
|
|
85
|
+
gameKey: game_key
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
data = connection.execute(query, variables)
|
|
89
|
+
|
|
90
|
+
return nil unless data['fetchGame']
|
|
91
|
+
|
|
92
|
+
Game.new(data['fetchGame'], client: client)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Fetch multiple games by IDs
|
|
97
|
+
#
|
|
98
|
+
# @param ids [Array<String>] Array of game UUIDs (max 100)
|
|
99
|
+
# @return [Array<Game>] Array of games
|
|
100
|
+
# @raise [ArgumentError] If more than 100 IDs provided
|
|
101
|
+
def fetch_many(ids)
|
|
102
|
+
raise ArgumentError, 'Maximum 100 IDs allowed' if ids.length > 100
|
|
103
|
+
|
|
104
|
+
query = QueryBuilder.fetch_games
|
|
105
|
+
data = connection.execute(query, { ids: ids })
|
|
106
|
+
|
|
107
|
+
(data['fetchGames'] || []).map { |g| Game.new(g, client: client) }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CardDB
|
|
4
|
+
module Resources
|
|
5
|
+
# Publishers resource for searching and fetching publishers
|
|
6
|
+
class Publishers < Base
|
|
7
|
+
# Search for publishers
|
|
8
|
+
#
|
|
9
|
+
# @param search [String, nil] Search by name
|
|
10
|
+
# @param first [Integer, nil] Maximum number of results
|
|
11
|
+
# @param after [String, nil] Cursor for pagination
|
|
12
|
+
# @return [Collection<Publisher>] Collection of publishers
|
|
13
|
+
def search(search: nil, first: nil, after: nil)
|
|
14
|
+
query = QueryBuilder.search_publishers(
|
|
15
|
+
search: search,
|
|
16
|
+
first: first,
|
|
17
|
+
after: after
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
variables = build_variables(
|
|
21
|
+
search: search,
|
|
22
|
+
first: first,
|
|
23
|
+
after: after
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
data = connection.execute(query, variables)
|
|
27
|
+
|
|
28
|
+
# Create next page loader
|
|
29
|
+
next_page_loader = lambda do |cursor|
|
|
30
|
+
search(
|
|
31
|
+
search: search,
|
|
32
|
+
first: first,
|
|
33
|
+
after: cursor
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Collection.new(
|
|
38
|
+
data['searchPublishers'],
|
|
39
|
+
item_class: Publisher,
|
|
40
|
+
next_page_loader: next_page_loader,
|
|
41
|
+
client: client
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Fetch a publisher by ID or slug
|
|
46
|
+
#
|
|
47
|
+
# @param id [String, nil] The publisher UUID
|
|
48
|
+
# @param slug [String, nil] The publisher slug
|
|
49
|
+
# @param cache [Boolean, nil] Whether to cache (nil = use config setting)
|
|
50
|
+
# @return [Publisher, nil] The publisher or nil if not found
|
|
51
|
+
# @raise [ArgumentError] If neither id nor slug is provided
|
|
52
|
+
def fetch(id: nil, slug: nil, cache: nil)
|
|
53
|
+
raise ArgumentError, 'Must provide either id or slug' if id.nil? && slug.nil?
|
|
54
|
+
|
|
55
|
+
key = cache_key('publishers', 'fetch', id: id, slug: slug)
|
|
56
|
+
with_cache(key, resource: :publishers, cache: cache) do
|
|
57
|
+
if id
|
|
58
|
+
query = QueryBuilder.fetch_publisher_by_id
|
|
59
|
+
data = connection.execute(query, { id: id })
|
|
60
|
+
else
|
|
61
|
+
query = QueryBuilder.fetch_publisher_by_slug
|
|
62
|
+
data = connection.execute(query, { slug: slug })
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
return nil unless data['fetchPublisher']
|
|
66
|
+
|
|
67
|
+
Publisher.new(data['fetchPublisher'], client: client)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Fetch multiple publishers by slugs
|
|
72
|
+
#
|
|
73
|
+
# @param slugs [Array<String>] Array of publisher slugs (max 100)
|
|
74
|
+
# @return [Array<Publisher>] Array of publishers
|
|
75
|
+
# @raise [ArgumentError] If more than 100 slugs provided
|
|
76
|
+
def fetch_many(slugs)
|
|
77
|
+
raise ArgumentError, 'Maximum 100 slugs allowed' if slugs.length > 100
|
|
78
|
+
|
|
79
|
+
query = QueryBuilder.fetch_publishers
|
|
80
|
+
data = connection.execute(query, { slugs: slugs })
|
|
81
|
+
|
|
82
|
+
(data['fetchPublishers'] || []).map { |p| Publisher.new(p, client: client) }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|