Add full Scryfall bulk data pipeline:
- Migration: 13 new columns on `cards` (oracle_id, illustration_id,
color_identity, keywords, legalities, flavor_text, artist, released_at,
layout, edhrec_rank, reserved, reprint, finishes) with GIN indexes
for JSONB search.
- Migration: `tags` + `card_tags` tables for Tagger community data.
- Script: `bulk-import-scryfall.js` — downloads Oracle Cards bulk file
(168 MB) and upserts all 36k+ MTG cards with rich metadata.
- Script: `import-scryfall-tags.js` — imports oracle tags (4.5k tags,
227k taggings) and art tags (11k tags, 458k taggings).
- Lib: `bulk-sync.js` — runtime bulk sync callable from the admin API.
- Admin UI: mode toggle (incremental vs bulk) on catalog sync panel.
Enables Commander deck validation (color_identity), format legality
checks, keyword search, EDHREC popularity ranking, and functional
card tagging ("removal", "ramp", "draw") for deck building assistance.
Co-authored-by: Cursor <cursoragent@cursor.com>
51 lines
1.9 KiB
JavaScript
51 lines
1.9 KiB
JavaScript
/**
|
|
* Add Scryfall bulk-data columns to `cards` for richer MTG metadata.
|
|
* These enable Tagger tag joins (oracle_id, illustration_id), Commander
|
|
* deck validation (color_identity), format legality checks, keyword
|
|
* search, and better card detail pages.
|
|
*/
|
|
export const shorthands = undefined;
|
|
|
|
export const up = (pgm) => {
|
|
pgm.addColumns('cards', {
|
|
oracle_id: { type: 'varchar(36)', comment: 'Stable across printings; joins oracle tags' },
|
|
illustration_id: { type: 'varchar(36)', comment: 'Stable per artwork; joins art tags' },
|
|
color_identity: { type: 'jsonb', comment: '["W","U","B","R","G"] — includes symbols in rules text' },
|
|
keywords: { type: 'jsonb', comment: '["Flying","Trample",...] — searchable mechanics' },
|
|
legalities: { type: 'jsonb', comment: '{standard:"legal", modern:"not_legal",...}' },
|
|
flavor_text: { type: 'text' },
|
|
artist: { type: 'varchar(255)' },
|
|
released_at: { type: 'date' },
|
|
layout: { type: 'varchar(50)', comment: 'normal, transform, split, mdfc, adventure, etc.' },
|
|
edhrec_rank: { type: 'integer', comment: 'Commander popularity — lower is more popular' },
|
|
reserved: { type: 'boolean', default: false },
|
|
reprint: { type: 'boolean', default: false },
|
|
finishes: { type: 'jsonb', comment: '["nonfoil","foil","etched"]' },
|
|
});
|
|
|
|
pgm.createIndex('cards', 'oracle_id');
|
|
pgm.createIndex('cards', 'illustration_id');
|
|
pgm.createIndex('cards', 'color_identity', { method: 'gin' });
|
|
pgm.createIndex('cards', 'keywords', { method: 'gin' });
|
|
pgm.createIndex('cards', 'legalities', { method: 'gin' });
|
|
pgm.createIndex('cards', 'artist');
|
|
pgm.createIndex('cards', 'edhrec_rank');
|
|
};
|
|
|
|
export const down = (pgm) => {
|
|
pgm.dropColumns('cards', [
|
|
'oracle_id',
|
|
'illustration_id',
|
|
'color_identity',
|
|
'keywords',
|
|
'legalities',
|
|
'flavor_text',
|
|
'artist',
|
|
'released_at',
|
|
'layout',
|
|
'edhrec_rank',
|
|
'reserved',
|
|
'reprint',
|
|
'finishes',
|
|
]);
|
|
};
|