Yu-Gi-Oh! Deck Building and Card Inventory Management web interface written in Common Lisp, utilizing HTMX.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ygoprodeck-json.lisp 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. #|
  2. src/models/ygoprodeck.json.lisp
  3. Version 3 JSON Importer
  4. TODO Write a test suite - it should be easier now that it's in FNF
  5. The idea behind this code is you'll be able to one click button download and update the db.
  6. (cl-deck-builder2.models.ygoprodeck.json2::json-import-cardinfo #P"/tmp/cardinfo.json")
  7. |#
  8. (in-package #:cl-user)
  9. (in-package #:cl-deck-builder2.models.ygoprodeck.json)
  10. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  11. (defvar *json* nil
  12. "The currently loaded CARDINFO data pulled from YGOProDeck API.")
  13. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  14. (defmethod cardinfo-import ((obj cardinfo) &optional force)
  15. "Set *JSON* to the decoded JSON data from CARDINFO, by processing it into CARDINFO-JSON using CARDINFO-CONVERT.
  16. Optionally, FORCE the data to be loaded anyway.
  17. If the (CARDINFO-OUTPUT OBJ) does not exist, try to (CARDINFO-INPUT *CARDINFO*)."
  18. (cardinfo-load obj force)
  19. (process-all-nodes))
  20. (defmethod cardinfo-load ((obj cardinfo) &optional force)
  21. (let ((output (cardinfo-output obj)))
  22. (v:info :ygoprodeck.json "CARDINFO-LOAD ~a (exists:~a) (force:~a)" output (cardinfo-output-exists-p obj) force)
  23. (if (cardinfo-output-exists-p obj)
  24. (progn
  25. (setf *json* (with-open-file (f output)
  26. (cl-json:decode-json f)))))))
  27. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  28. (defun json-cardinfo-base-fields (plist)
  29. "Filter only the base fields for this CARD-INFO PLIST."
  30. (set-difference plist
  31. '((:banlist--info)
  32. (:card--images)
  33. (:card--prices)
  34. (:card--sets)
  35. (:linkmarkers)
  36. (:misc--info))
  37. :key #'car))
  38. ;; (defparameter +ygo-card-ids+ (make-hash-table :test #'equal))
  39. ;; (defparameter +ygo-card-descs+ (make-hash-table :test #'equal))
  40. ;; TODO put all of these hash tables inside another hash table, then
  41. ;; make an API around it. I've seen that done before with CLOS, maybe
  42. ;; in cl-opengl defvao stuff. Remember?
  43. (defparameter +ygo-card-names+ (make-hash-table :test #'equal))
  44. (defparameter +ygo-card-types+ (make-hash-table :test #'equal))
  45. (defparameter +ygo-card-races+ (make-hash-table :test #'equal))
  46. (defparameter +ygo-card-frame-types+ (make-hash-table :test #'equal))
  47. (defparameter +ygo-card-archetypes+ (make-hash-table :test #'equal))
  48. (defparameter +ygo-card-attributes+ (make-hash-table :test #'equal))
  49. (defparameter +ygo-set-names+ (make-hash-table :test #'equal))
  50. (defparameter +ygo-set-codes+ (make-hash-table :test #'equal))
  51. (defparameter +ygo-set-rarity+ (make-hash-table :test #'equal))
  52. (defparameter +ygo-set-editions+ (make-hash-table :test #'equal))
  53. ;; Here we use the ID/PASSCODE so EQ is Okay
  54. (defparameter +ygo-card-prices+ (make-hash-table)) ;; One to one: setf place
  55. (defparameter +ygo-alternative-artwork+ (make-hash-table)) ;; Many to many: push place
  56. (defparameter +ygo-cards+ (make-hash-table)) ;; One to one: setf place
  57. (defparameter +ygo-sets+ (make-hash-table)) ;; Many to many: push place
  58. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  59. ;; Field Names
  60. (defparameter +ygo-banlist-info+ (make-hash-table))
  61. (defparameter +ygo-format-info+ (make-hash-table))
  62. (defparameter +ygo-linkmarker-info+ (make-hash-table))
  63. (defparameter +ygo-banlist-names+ (make-hash-table :test #'equal))
  64. (defparameter +ygo-format-names+ (make-hash-table :test #'equal))
  65. (defparameter +ygo-linkmarker-names+ (make-hash-table :test #'equal))
  66. (defparameter +ygo-set-rarity-codes+ (make-hash-table :test #'equal))
  67. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  68. ;; The bulk of the operation ... FIND-OR-MAKE-INSTANCE + SYNC-HASH-TABLE
  69. (defun sync-hash-table (ht)
  70. (let ((values (alexandria:hash-table-values ht)))
  71. (cond ((null values) nil)
  72. (;; The ht is a bunch of lists (card sets, prices, images)
  73. (listp (car values))
  74. (dolist (lst values)
  75. (unless (mito:dao-synced (car lst))
  76. (do-grouped-insert lst))))
  77. (t
  78. (unless (mito:dao-synced (car values))
  79. (do-grouped-insert values))))))
  80. ;; TODO Where should this go? I'm seeing bits of code like this all over.
  81. (defun %keywordify (s)
  82. (alexandria:make-keyword
  83. (string-upcase
  84. (substitute #\- #\Space s))))
  85. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  86. ;; Importer v3
  87. (defun extract-json-tag (json tag)
  88. "Get all the tags from JSON matching TAG."
  89. (mapcar (lambda (alist) (assoc-utils:aget alist tag)) json))
  90. (defun extract-concat (json tag)
  91. "Concatenate the results of EXTRACT-JSON-TAG into a single list for MAPCAR'ing."
  92. (apply #'concatenate 'list (extract-json-tag json tag)))
  93. (defun extract-card-sets (json)
  94. "Just extract :CARD--SETS. No Processing"
  95. (extract-concat json :card--sets))
  96. (defun extract-card-set-map (json)
  97. "Extract PASSCODE => SETS mapping."
  98. (let ((lst '()))
  99. (dolist (node json (copy-alist
  100. (apply #'concatenate 'list (reverse lst))))
  101. (let* ((node-id (assoc-utils:aget node :id))
  102. (maybe-card-sets
  103. (mapcar (lambda (alist)
  104. (acons :passcode-id node-id alist))
  105. (assoc-utils:aget node :card--sets))))
  106. (when maybe-card-sets
  107. (push maybe-card-sets lst))))))
  108. (defun extract-card-images (json)
  109. "Just extract :CARD--IMAGES. No Processing"
  110. (extract-concat json :card--images))
  111. (defun extract-card-alternative-images-map (json)
  112. "Mapping of ID to alternative art IDs."
  113. (let ((lst '()))
  114. (dolist (node json (reverse lst))
  115. (let* ((alternate-id (assoc-utils:aget node :id))
  116. (maybe-alternative-images
  117. (remove alternate-id
  118. (mapcar (lambda (alist)
  119. (assoc-utils:aget alist :id))
  120. (assoc-utils:aget node :card--images)))))
  121. (when maybe-alternative-images
  122. (mapcar (lambda (node-id)
  123. (push (list :passcode-id node-id
  124. :alternate-id alternate-id)
  125. lst))
  126. maybe-alternative-images))))))
  127. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  128. ;; Working with the base fields
  129. (defun extract-card-tag (json tag)
  130. "Extract TAG from JSON using EXTRACT-JSON-TAG. Also, SORT, and SUBSTITUTE NIL for \"\"."
  131. (sort
  132. (substitute "" NIL
  133. (remove-duplicates
  134. (extract-json-tag json tag)
  135. :test #'string=))
  136. #'string<))
  137. (defun extract-card-name (json)
  138. (extract-card-tag json :name))
  139. (defun extract-card-type (json)
  140. (extract-card-tag json :type))
  141. (defun extract-card-race (json)
  142. (extract-card-tag json :race))
  143. (defun extract-card-frame-type (json)
  144. (extract-card-tag json :frame-type))
  145. (defun extract-card-archetype (json)
  146. (extract-card-tag json :archetype))
  147. (defun extract-card-attribute (json)
  148. (extract-card-tag json :attribute))
  149. (defun extract-cards (json)
  150. "Just extract the base field for every card."
  151. (mapcar #'json-cardinfo-base-fields json))
  152. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  153. (defun extract-card-banlist-info (json)
  154. "Lots of cards don't have banlist info records."
  155. (let ((lst '()))
  156. (dolist (node json (copy-list lst))
  157. (let ((node-id (assoc-utils:aget node :id))
  158. (maybe-banlist-info
  159. (assoc-utils:alist-plist
  160. (assoc-utils:aget node :banlist--info))))
  161. (when maybe-banlist-info
  162. (push (append (list :passcode-id node-id)
  163. maybe-banlist-info)
  164. lst))))))
  165. (defun extract-card-banlist-names (json)
  166. "Just extract the names of the ban status. \"Banned\" etc."
  167. (let ((lst '()))
  168. (dolist (node json (copy-list lst))
  169. (let ((maybe-banlist-names
  170. (assoc-utils:alist-values
  171. (assoc-utils:aget node :banlist--info))))
  172. (when maybe-banlist-names
  173. (loop for name in maybe-banlist-names do
  174. (pushnew name lst :test #'string=)))))))
  175. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  176. (defun extract-card-prices (json)
  177. (extract-json-tag json :card--prices))
  178. (defun extract-card-price-map (json)
  179. "Extract :CARD--PRICE and process into ID => PRICES mapping"
  180. (let ((lst '()))
  181. (dolist (node json lst)
  182. (let ((node-id (assoc-utils:aget node :id))
  183. (maybe-price-data
  184. (assoc-utils:alist-plist
  185. (apply #'concatenate 'list
  186. (assoc-utils:aget node :card--prices)))))
  187. (when maybe-price-data
  188. (push (append (list :passcode-id node-id)
  189. maybe-price-data)
  190. lst))))))
  191. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  192. (defun extract-card-misc-info (json)
  193. (extract-json-tag json :misc--info))
  194. (defun extract-card-misc-info-map (json)
  195. "Extract :MISC--INFO and process into ID => FORMATS mapping"
  196. (let ((lst '()))
  197. (dolist (node json lst)
  198. (let* ((node-id (assoc-utils:aget node :id))
  199. (maybe-misc-info
  200. (assoc-utils:alist-plist
  201. (remove :formats
  202. (apply #'concatenate 'list
  203. (assoc-utils:aget node :misc--info))
  204. :key #'car))))
  205. (when maybe-misc-info
  206. (push (append (list :passcode-id node-id)
  207. maybe-misc-info)
  208. lst))))))
  209. ;; TODO
  210. (defun extract-card-format-info (json)
  211. "Extract :MISC--INFO and process into ID => FORMATS mapping"
  212. (let ((lst '()))
  213. (dolist (node json lst)
  214. (let* ((node-id (assoc-utils:aget node :id))
  215. (maybe-misc-info
  216. (assoc-utils:aget
  217. (apply #'concatenate 'list
  218. (assoc-utils:aget node :misc--info))
  219. :formats)))
  220. (when maybe-misc-info
  221. (push (list node-id maybe-misc-info) lst))))))
  222. ;; TODO - The card formats need to be processed like the card-sets and cards
  223. (defun process-card-format-info-map (json)
  224. (let ((info (extract-card-format-info json)))
  225. (with-connection (db)
  226. (with-transaction
  227. (mapcar (lambda (row)
  228. (list :passcode-id (car row)
  229. (apply #'concatenate 'list
  230. (mapcar (lambda (tag)
  231. (let ((db-tag (or (find-or-create-instance 'ygo-format-name :name tag))))
  232. (list (%keywordify tag) db-tag)))
  233. (cadr row)))))
  234. info)))))
  235. (defun extract-card-format-names (json)
  236. (sort
  237. (remove-duplicates
  238. (apply #'concatenate 'list
  239. (mapcar (lambda (alist) (assoc-utils:aget alist :formats))
  240. (apply #'concatenate 'list
  241. (extract-card-misc-info json))))
  242. :test #'string=)
  243. #'string<))
  244. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  245. (defun extract-card-linkmarker-names (json)
  246. (sort (remove-duplicates
  247. (extract-concat json :linkmarkers)
  248. :test #'string=)
  249. #'string<))
  250. ;; TODO
  251. (defun extract-card-linkmarker-info (json)
  252. "Just extract :CARD--IMAGES. No Processing"
  253. (let ((lst '()))
  254. (dolist (node json lst)
  255. (let ((node-id (assoc-utils:aget node :id))
  256. (maybe-linkmarkers
  257. (assoc-utils:aget node :linkmarkers)))
  258. (when maybe-linkmarkers
  259. (push (list node-id maybe-linkmarkers) lst))))))
  260. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  261. ;; Working with CARD--SETS
  262. ;; Here, JSON shuld be: (extract-card-sets json)
  263. (defun extract-set-tag (json tag)
  264. (sort (remove-duplicates
  265. (extract-json-tag json tag)
  266. :test #'string=)
  267. #'string<))
  268. (defun extract-set-name (json)
  269. (extract-set-tag json :set--name))
  270. (defun extract-set-code (json)
  271. (extract-set-tag json :set--code))
  272. (defun extract-set-rarity (json)
  273. (extract-set-tag json :set--rarity))
  274. (defun extract-set-rarity-code (json)
  275. (extract-set-tag json :set--rarity--code))
  276. (defun extract-set-edition (json)
  277. (extract-set-tag json :set--edition))
  278. (defun extract-set-price (json)
  279. (extract-set-tag json :set--price))
  280. (defun extract-set-url (json)
  281. (extract-set-tag json :set--url))
  282. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  283. ;; Card Images
  284. (defun extract-card-main-image (json)
  285. "From :CARD--IMAGES, extract the first IMAGE--URL. The other two are small and cropped images."
  286. (sort
  287. (mapcar (lambda (alist) (assoc-utils:aget alist :image--url))
  288. (extract-concat json :card--images))
  289. #'string<))
  290. (defun gen-hash-table (ht class tags)
  291. "Just a list mapping like \"Normal\" => 1, \"Tested\" => 2, and so on. Could be used for anything that has a NAME column."
  292. (v:info :ygoprodeck.json "GEN-HASH-TABLE: ~a" class)
  293. (with-connection (db)
  294. (with-transaction
  295. (dolist (tag tags ht)
  296. (unless (gethash tag ht)
  297. (setf (gethash tag ht)
  298. (or (mito:find-dao class :name tag)
  299. (mito:create-dao class :name tag))))))))
  300. (defun gen-hash-mapping-table (class tags &key (test 'equal))
  301. "A more complex mapping of PASSCODE -> DATA"
  302. (let ((ht (make-hash-table :test test)))
  303. (with-connection (db)
  304. (with-transaction
  305. (dolist (node tags ht)
  306. (let ((passcode-id (getf node :passcode-id)))
  307. (setf (gethash passcode-id ht)
  308. (or (mito:find-dao class :passcode-id passcode-id)
  309. (apply #'mito:create-dao class node)))))))))
  310. (defun gen-hash-map-list-table (class tags &key (test 'equal))
  311. "A more complex mapping of PASSCODE -> (DATA, DATA, DATA)"
  312. (let ((ht (make-hash-table :test test)))
  313. (with-connection (db)
  314. (with-transaction
  315. (dolist (node tags ht)
  316. (let ((passcode-id (getf node :passcode-id)))
  317. (push (or (apply #'mito:find-dao class node)
  318. (apply #'mito:create-dao class node))
  319. (gethash passcode-id ht))))))))
  320. (defun process-card-nodes (json)
  321. "Mostly preserved from V2."
  322. (let* ((base-fields (json-cardinfo-base-fields json))
  323. (id (assoc-utils:aget base-fields :id))
  324. (args (list
  325. :name (assoc-utils:aget base-fields :name)
  326. :desc (assoc-utils:aget base-fields :desc)
  327. :archetype (gethash (assoc-utils:aget base-fields :archetype) +ygo-card-archetypes+)
  328. :attribute (gethash (assoc-utils:aget base-fields :attribute) +ygo-card-attributes+)
  329. :frame-type (gethash (assoc-utils:aget base-fields :frame-type) +ygo-card-frame-types+)
  330. :race (gethash (assoc-utils:aget base-fields :race) +ygo-card-races+)
  331. :card-type (gethash (assoc-utils:aget base-fields :type) +ygo-card-types+)
  332. :atk (assoc-utils:aget base-fields :atk)
  333. :def (assoc-utils:aget base-fields :def)
  334. :scale (assoc-utils:aget base-fields :scale)
  335. :level (assoc-utils:aget base-fields :level)))
  336. (found (or (mito:find-dao 'ygo-card :id id)
  337. (apply #'create-dao 'ygo-card :id id args))))
  338. (unless (gethash id +ygo-cards+)
  339. (setf (gethash id +ygo-cards+) found))))
  340. (defun process-card-sets (&rest sets)
  341. "Mostly preserved from V2."
  342. (dolist (node sets +ygo-sets+)
  343. (let* ((args
  344. (list :passcode-id (assoc-utils:aget node :passcode-id)
  345. :code (gethash (assoc-utils:aget node :set--code) +ygo-set-codes+)
  346. :name (gethash (assoc-utils:aget node :set--name) +ygo-set-names+)
  347. :edition (gethash (assoc-utils:aget node :set--edition) +ygo-set-editions+)
  348. :rarity (gethash (assoc-utils:aget node :set--rarity) +ygo-set-rarity+)
  349. :rarity-code (gethash (assoc-utils:aget node :set--rarity--code) +ygo-set-rarity-codes+)))
  350. (rest-args
  351. (list :price (assoc-utils:aget node :set--price)
  352. :url (assoc-utils:aget node :set--url)))
  353. (found (or (apply #'mito:find-dao 'ygo-set args)
  354. (apply #'mito:create-dao 'ygo-set (append args rest-args)))))
  355. (pushnew found
  356. (gethash (assoc-utils:aget node :passcode-id) +ygo-sets+)
  357. :test #'mito:object=))))
  358. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  359. ;; TODO for Import-v4 - It might be useful to have PASSCODE as the ID,
  360. ;; that way, there is a uniform interface for querying card info...
  361. ;;
  362. ;; TODO Split this up?
  363. (defun pre-process-nodes (json)
  364. (let ((card-names (extract-card-name json))
  365. (card-types (extract-card-type json))
  366. (card-races (extract-card-race json))
  367. (card-frame-types (extract-card-frame-type json))
  368. (card-archetypes (extract-card-archetype json))
  369. (card-attributes (extract-card-attribute json))
  370. (card-sets (extract-card-sets json)))
  371. (gen-hash-table +ygo-card-names+ 'ygo-card-name card-names)
  372. (gen-hash-table +ygo-card-archetypes+ 'ygo-card-archetype card-archetypes)
  373. (gen-hash-table +ygo-card-attributes+ 'ygo-card-attribute card-attributes)
  374. (gen-hash-table +ygo-card-frame-types+ 'ygo-card-frame-type card-frame-types)
  375. (gen-hash-table +ygo-card-races+ 'ygo-card-race card-races)
  376. (gen-hash-table +ygo-card-types+ 'ygo-card-type card-types)
  377. (gen-hash-table +ygo-set-names+ 'ygo-set-name (extract-set-name card-sets))
  378. (gen-hash-table +ygo-set-codes+ 'ygo-set-code (extract-set-code card-sets))
  379. (gen-hash-table +ygo-set-rarity+ 'ygo-set-rarity (extract-set-rarity card-sets))
  380. (gen-hash-table +ygo-set-editions+ 'ygo-set-edition (extract-set-edition card-sets))
  381. (gen-hash-table +ygo-set-rarity-codes+ 'ygo-set-rarity-code (extract-set-rarity-code card-sets))
  382. (gen-hash-table +ygo-banlist-names+ 'ygo-banlist-name (extract-card-banlist-info card-sets))
  383. (gen-hash-table +ygo-linkmarker-names+ 'ygo-linkmarker-name (extract-card-linkmarker-names card-sets))
  384. (gen-hash-table +ygo-format-names+ 'ygo-format-name (extract-card-format-names json))))
  385. (defun pre-process-card-prices (json)
  386. (setf +ygo-card-prices+ (gen-hash-mapping-table 'ygo-price (extract-card-price-map json) :test 'eq)))
  387. (defun pre-process-card-alternative-artwork (json)
  388. (setf +ygo-alternative-artwork+ (gen-hash-map-list-table 'ygo-alternative-artwork (extract-card-alternative-images-map json) :test 'eq)))
  389. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  390. (defun process-all-nodes (&optional (json *json*))
  391. "Process all nodes from JSON."
  392. (v:info :ygoprodeck.json "PROCESS-ALL-NODES: PRE-PROCESS-NODES")
  393. (pre-process-nodes json)
  394. (pre-process-card-prices json)
  395. (pre-process-card-alternative-artwork json)
  396. ;; +ygo-cards+
  397. (v:info :ygoprodeck.json "PROCESS-ALL-NODES: PROCESS-CARD-NODES")
  398. (with-connection (db)
  399. (with-transaction
  400. (mapcan #'process-card-nodes json)))
  401. ;; +ygo-sets+
  402. (v:info :ygoprodeck.json "PROCESS-ALL-NODES: PROCESS-CARD-SETS")
  403. (with-connection (db)
  404. (with-transaction
  405. (apply #'process-card-sets (extract-card-set-map json)))))