1
0
mirror of https://github.com/tcgdex/cards-database.git synced 2025-08-16 09:08:52 +00:00

feat: make build valid

This commit is contained in:
2025-08-05 23:10:01 +02:00
parent 66e7a10d9f
commit 2c746914e9
9 changed files with 144 additions and 74 deletions

View File

@@ -4,6 +4,7 @@
"": {
"name": "@tcgdex/server",
"dependencies": {
"@cachex/memory": "^1.0.1",
"@dzeio/config": "^1.1.12",
"@dzeio/object-util": "^1.9.1",
"@dzeio/queue": "^1.2.1",
@@ -22,6 +23,7 @@
"@types/express": "^4.17.21",
"@types/node": "^20.17.32",
"@types/swagger-ui-express": "^4.1.8",
"@typescript/native-preview": "^7.0.0-dev.20250805.1",
"glob": "^10.4.5",
"typescript": "^4.9.5",
},
@@ -170,6 +172,22 @@
"@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="],
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20250805.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250805.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250805.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20250805.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250805.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20250805.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250805.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20250805.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-Ma1BWxSp/T4cKQgW3LJSn0n9zfT6WMDvEsroubbP68PMMchGwZNlhLmbzBbczoMZDXWbo88oPCmEPPGuCIlVXQ=="],
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20250805.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zde7Znjvzx+leJmOCR8FxTBG14sw8r6o8Uz9gaFjv5ziM84NxsRWH2x7Qi92mtC7Dpjl6HFyMH0O7V2MnQBiAA=="],
"@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20250805.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/SPr/zuISYr61JNF8Y6H3hz0pAjqPUPI+K1tpKdPytdddLsHUL0JPV6Lo0jRHyGchUyBINQa3Ur5xgmEEHHWsg=="],
"@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20250805.1", "", { "os": "linux", "cpu": "arm" }, "sha512-1uHvm/fadBA9RsgDAgQqh5s0Fnpp0GF/AfraD1gCkUYTIWOQKa00SSnuWKNgZLEoUn6mceVjAh4bpetOZrnB1Q=="],
"@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20250805.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-YGD2wrzoRta9avc7tmCSQ7Bkp6darqtG7Sk6oFqoM0x/hqzWM1iNUNbDDkcuUqRhwT6lIileKeOCg0idmlwPfg=="],
"@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20250805.1", "", { "os": "linux", "cpu": "x64" }, "sha512-VJbCgCEyPi+zL4ZIaASKwPpzm5hWB11mu+9R7Ut+hX9NQ6WaighiNJilWgCKNxa7/7BsAqa2VGTGUcz+62qWKA=="],
"@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20250805.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-qptMfyam9YSkKSQ0aF5HrgIkBGgpASsD8q88yN5RhA4irbf0GO5phmii3BzKYS93PN3saYVW6DVJQYhBh9qy6g=="],
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20250805.1", "", { "os": "win32", "cpu": "x64" }, "sha512-urtTmSfc8/CkE8zWV3+WN5k8rbBZqYaP2xQeNbkJNAO0+eM7DdKvWzen1ehJuHkk3TJ5t4mLj45G4nN50D2PIQ=="],
"abs": ["abs@1.3.15", "", { "dependencies": { "ul": "^5.0.0" } }, "sha512-bpFChpVyZ2F2ppgx7qjZ5TTEO6VVwBauUZDZibpclRGhfcXTHyj11nlqwrg5dN1knxCchssROehm76uCcCayRA=="],
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],

View File

@@ -5,11 +5,12 @@
"scripts": {
"compile": "bun compiler/index.ts",
"dev": "bun --watch src/index.ts",
"validate": "tsc --noEmit --project ./tsconfig.json",
"validate": "tsgo --noEmit --project ./tsconfig.json",
"start": "bun src/index.ts"
},
"license": "MIT",
"dependencies": {
"@cachex/memory": "^1.0.1",
"@dzeio/config": "^1.1.12",
"@dzeio/object-util": "^1.9.1",
"@dzeio/queue": "^1.2.1",

View File

@@ -1,7 +1,11 @@
import Cache from '@cachex/memory'
import { objectOmit } from '@dzeio/object-util'
import type { CardResume, Card as SDKCard } from '@tcgdex/sdk'
import { SupportedLanguages } from '@tcgdex/sdk'
import de from '../../../generated/de/cards.json'
import en from '../../../generated/en/cards.json'
import es from '../../../generated/es/cards.json'
import esmx from '../../../generated/es-mx/cards.json'
import es from '../../../generated/es/cards.json'
import fr from '../../../generated/fr/cards.json'
import id from '../../../generated/id/cards.json'
import it from '../../../generated/it/cards.json'
@@ -16,12 +20,43 @@ import ru from '../../../generated/ru/cards.json'
import th from '../../../generated/th/cards.json'
import zhcn from '../../../generated/zh-cn/cards.json'
import zhtw from '../../../generated/zh-tw/cards.json'
import { SupportedLanguages } from '@tcgdex/sdk'
import type { CardResume, Card as SDKCard } from '@tcgdex/sdk'
import { executeQuery, type Query } from '../../libs/QueryEngine/filter'
import { objectOmit } from '@dzeio/object-util'
import { getCardMarketPrice } from '../../libs/providers/cardmarket'
import { getTCGPlayerPrice } from '../../libs/providers/tcgplayer'
import { executeQuery, type Query } from '../../libs/QueryEngine/filter'
// any is CompiledCard that is currently not mapped correctly
const list: Record<`${string|any}${SupportedLanguages|string}`, any> = {}
// @ts-ignore ts can't load file
en.forEach((it) => list[`${it.id}en`] = it)
// @ts-ignore ts can't load file
fr.forEach((it) => list[`${it.id}fr`] = it)
// @ts-ignore ts can't load file
es.forEach((it) => list[`${it.id}es`] = it)
esmx.forEach((it) => list[`${it.id}es-mx`] = it)
// @ts-ignore ts can't load file
it.forEach((it) => list[`${it.id}it`] = it)
// @ts-ignore ts can't load file
pt.forEach((it) => list[`${it.id}pt`] = it)
ptbr.forEach((it) => list[`${it.id}pt-br`] = it)
// @ts-expect-error there is currently not cards here
ptpt.forEach((it) => list[`${it.id}pt-pt`] = it)
// @ts-ignore ts can't load file
de.forEach((it) => list[`${it.id}de`] = it)
// @ts-expect-error there is currently not cards here
nl.forEach((it) => list[`${it.id}nl`] = it)
// @ts-expect-error there is currently not cards here
pl.forEach((it) => list[`${it.id}pl`] = it)
// @ts-expect-error there is currently not cards here
ru.forEach((it) => list[`${it.id}ru`] = it)
ja.forEach((it) => list[`${it.id}ja`] = it)
// @ts-expect-error there is currently not cards here
ko.forEach((it) => list[`${it.id}ko`] = it)
// @ts-ignore ts can't load file
zhtw.forEach((it) => list[`${it.id}zh-tw`] = it)
id.forEach((it) => list[`${it.id}id`] = it)
th.forEach((it) => list[`${it.id}th`] = it)
zhcn.forEach((it) => list[`${it.id}zh-cn`] = it)
const cards = {
en: en,
@@ -44,28 +79,61 @@ const cards = {
'zh-cn': zhcn,
} as const
type MappedCard = any // (typeof en)[number]
const cache = new Cache()
type MappedCard = SDKCard // (typeof en)[number]
export type Card = SDKCard
export async function getAllCards(lang: SupportedLanguages): Promise<Array<SDKCard>> {
return Promise.all((cards[lang] as Array<MappedCard>).map(transformCard))
return Promise.all((cards[lang] as Array<MappedCard>).map((it) => loadCard(lang, it.id)))
}
async function transformCard(card: MappedCard): Promise<SDKCard> {
console.time('cardmarket')
const cardmarket = await getCardMarketPrice(card)
console.timeEnd('cardmarket')
console.time('tcgplayer')
const tcgplayer = await getTCGPlayerPrice(card)
console.timeEnd('tcgplayer')
return {
/**
* Function that do the hard work of loading the card with the external processors
*
* It should run once until it's timeout runout :D
* @param lang
* @param id
*/
async function loadCard(lang: SupportedLanguages, id: string): Promise<SDKCard> {
const key = `${id}${lang}`
const value = cache.get<SDKCard>(key)
// expect the cache to be present
if (value) {
return value
}
// console.time(`loading card ${id}${lang}`)
// console.time('fetching DB')
// @ts-expect-error flemme
const card = list[key]
// console.timeEnd('fetching DB')
// console.time('loading providers')
const [cardmarket, tcgplayer] = await Promise.all([
getCardMarketPrice(card),
getTCGPlayerPrice(card),
])
// console.timeEnd('loading providers')
// console.time('remapping card')
const res = {
...objectOmit(card, 'thirdParty'),
pricing: {
cardmarket: cardmarket,
tcgplayer: tcgplayer
}
}
} as SDKCard
// console.timeEnd('remapping card')
cache.set(key, res, 60 * 60)
// console.timeEnd(`loading card ${id}${lang}`)
return res
}
export async function getCardById(lang: SupportedLanguages, id: string) {
return loadCard(lang, id)
}
export async function findCards(lang: SupportedLanguages, query: Query<SDKCard>) {

View File

@@ -20,7 +20,6 @@ import th from '../../../generated/th/series.json'
import zhcn from '../../../generated/zh-cn/series.json'
import zhtw from '../../../generated/zh-tw/series.json'
const series = {
en: en,
fr: fr,

View File

@@ -6,7 +6,7 @@ import { Errors, sendError } from '../../libs/Errors'
import type { Query } from '../../libs/QueryEngine/filter'
import { recordToQuery } from '../../libs/QueryEngine/parsers'
import { betterSorter, checkLanguage, unique } from '../../util'
import { getAllCards, findOneCard, findCards, toBrief } from '../Components/Card'
import { getAllCards, findOneCard, findCards, toBrief, getCardById } from '../Components/Card'
import { findOneSet, findSets, setToBrief } from '../Components/Set'
import { findOneSerie, findSeries, serieToBrief } from '../Components/Serie'
@@ -209,7 +209,7 @@ server
* ex: /v2/en/cards/base1-1
*/
.get('/:lang/:endpoint/:id', async (req: CustomRequest, res) => {
console.time('request')
// console.time('request')
let { id, lang, endpoint } = req.params
if (id.endsWith('.json')) {
@@ -225,10 +225,12 @@ server
let result: unknown
switch (endpoint) {
case 'cards':
result = await findOneCard(lang, { id })
// console.time('card')
result = await getCardById(lang, id)
if (!result) {
result = await findOneCard(lang, { name: id })
}
// console.timeEnd('card')
break
case 'sets':
@@ -264,7 +266,7 @@ server
}
}
console.timeEnd('request')
// console.timeEnd('request')
if (!result) {
sendError(Errors.NOT_FOUND, res)
return

View File

@@ -2,8 +2,9 @@ import express from 'express'
import fs from 'node:fs'
import { buildSchema, type GraphQLError } from 'graphql'
import { createHandler } from 'graphql-http/lib/use/express'
/** @ts-expect-error idk why this error in tsgo but not in tsc */
import { type ruruHTML as RuruHTML } from 'ruru/dist/server'
/** @ts-expect-error typing is not correctly mapped (real type at ruru/dist/server.d.ts) */
// /** @ts-expect-error typing is not correctly mapped (real type at ruru/dist/server.d.ts) */
import { makeHTMLParts, ruruHTML as tmp } from 'ruru/server'
import resolver from './resolver'

View File

@@ -17,9 +17,8 @@ export interface Result {
subTypeName: 'Normal' | 'Reverse Holofoil' | 'Holofoil'
}
let cache: Record<number, Record<string, Result>> = {}
let lastFetch: Date | undefined = undefined
let variantsCache: Map<number, Array<Result['subTypeName']>> = new Map()
let dataCache: Map<`${number}-${Result['subTypeName']}`, Result> = new Map()
let lastUpdate: Date | undefined = undefined
export async function updateTCGPlayerDatas(): Promise<boolean> {
@@ -52,14 +51,13 @@ export async function updateTCGPlayerDatas(): Promise<boolean> {
const data = await res.json()
// console.log('data:', data)
for (const item of data.results) {
dataCache.set(`${item.productId}-${item.subTypeName}`, item)
const variants = variantsCache.get(item.productId) ?? []
if (!variants.includes(item.subTypeName)) {
variants.push(item.subTypeName)
variantsCache.set(item.productId, variants)
const cacheItem = cache[item.productId] ?? {}
if (!(item.subTypeName in cacheItem)) {
cacheItem[variantMapping[item.subTypeName] ?? item.subTypeName] = objectOmit(item, 'productId', 'subTypeName')
}
cache[item.productId] = cacheItem
}
}
@@ -69,11 +67,11 @@ export async function updateTCGPlayerDatas(): Promise<boolean> {
return true
}
const variantMapping = {
const variantMapping: Record<string, string> = {
Normal: 'normal',
'Reverse Holofoil': 'reverse',
'Holofoil': 'holo'
} as const satisfies Record<Result['subTypeName'], string>
}
export async function getTCGPlayerPrice(card: { thirdParty: { tcgplayer?: number } }): Promise<{
unit: 'USD',
@@ -85,23 +83,14 @@ export async function getTCGPlayerPrice(card: { thirdParty: { tcgplayer?: number
if (!lastFetch || typeof card.thirdParty?.tcgplayer !== 'number') {
return null
}
const variants = variantsCache.get(card.thirdParty.tcgplayer!)
const variants = cache[card.thirdParty.tcgplayer!]
if (!variants) {
return null
}
const res: NonNullable<Awaited<ReturnType<typeof getTCGPlayerPrice>>> = {
updated: (lastUpdate ?? lastFetch).toISOString(),
unit: 'USD'
unit: 'USD',
...variants
}
for (const variant of variants) {
const input = dataCache.get(`${card.thirdParty!.tcgplayer}-${variant}`)
if (!input) {
continue
}
res[variantMapping[variant] as 'normal'] = objectOmit(input, 'productId', 'subTypeName')
}
// if (!input) {
// return null
// }
return res
}

View File

@@ -51,9 +51,8 @@ async function getToken() {
return bearer?.access_token
}
let cache: Record<number, Record<string, Result>> = {}
let lastFetch: Date | undefined = undefined
let variantsCache: Map<number, Array<Result['subTypeName']>> = new Map()
let dataCache: Map<`${number}-${Result['subTypeName']}`, Result> = new Map()
export async function updateTCGPlayerDatas(): Promise<boolean> {
const token = await getToken()
@@ -77,12 +76,12 @@ export async function updateTCGPlayerDatas(): Promise<boolean> {
})
.then((res) => res.json() as Promise<Root>)
for (const item of data.results) {
dataCache.set(`${item.productId}-${item.subTypeName}`, item)
const variants = variantsCache.get(item.productId) ?? []
if (!variants.includes(item.subTypeName)) {
variants.push(item.subTypeName)
variantsCache.set(item.productId, variants)
const cacheItem = cache[item.productId] ?? {}
if (!(item.subTypeName in cacheItem)) {
cacheItem[variantMapping[item.subTypeName] ?? item.subTypeName] = objectOmit(item, 'productId', 'subTypeName')
}
cache[item.productId] = cacheItem
}
}
@@ -91,11 +90,11 @@ export async function updateTCGPlayerDatas(): Promise<boolean> {
return true
}
const variantMapping = {
const variantMapping: Record<string, string> = {
Normal: 'normal',
'Reverse Holofoil': 'reverse',
'Holofoil': 'holo'
} as const satisfies Record<Result['subTypeName'], string>
}
export async function getTCGPlayerPrice(card: { thirdParty: { tcgplayer?: number } }): Promise<{
unit: 'USD',
@@ -107,21 +106,14 @@ export async function getTCGPlayerPrice(card: { thirdParty: { tcgplayer?: number
if (!lastFetch || typeof card.thirdParty?.tcgplayer !== 'number') {
return null
}
const variants = variantsCache.get(card.thirdParty.tcgplayer!)
const variants = cache[card.thirdParty.tcgplayer!]
if (!variants) {
return null
}
const res: NonNullable<Awaited<ReturnType<typeof getTCGPlayerPrice>>> = {
updated: lastFetch.toISOString(),
unit: 'USD'
unit: 'USD',
...variants
}
for (const variant of variants) {
const input = dataCache.get(`${card.thirdParty!.tcgplayer}-${variant}`)
if (!input) {
continue
}
res[variantMapping[variant] as 'normal'] = objectOmit(input, 'productId', 'subTypeName')
}
return res
}

View File

@@ -1,7 +1,7 @@
import { objectKeys, objectLoop, objectMap } from '@dzeio/object-util'
import express from 'express'
import * as Serie from './V2/Components/Serie'
import * as Set from './V2/Components/Set'
import { findOneSerie } from './V2/Components/Serie'
import { findOneSet } from './V2/Components/Set'
import de from '../generated/de/stats.json'
import en from '../generated/en/stats.json'
@@ -125,7 +125,7 @@ objectLoop(langs, (stats, key) => preProcessSets(stats, key))
// Yes this is ugly
export default express.Router()
.get('/github.svg', (_, res): void => {
.get('/github.svg', async (_, res): Promise<void> => {
res.setHeader('Content-Type', 'image/svg+xml')
res.send(`<svg width="1429" height="726" viewBox="0 0 1429 726" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="1429" height="726" fill="white"/>
@@ -217,7 +217,7 @@ export default express.Router()
<text fill="#212121" xml:space="preserve" style="white-space: pre" font-family="Arial" font-size="16" font-weight="600" letter-spacing="0em"><tspan x="1305.93" y="652">${totalAsia.count + totalAsia.images}&#10;</tspan><tspan x="1325.82" y="672">of&#10;</tspan><tspan x="1305.93" y="692">${totalAsia.total * 2}&#10;</tspan><tspan x="1309.17" y="712">(${(100 * (totalAsia.count + totalAsia.images) / (totalAsia.total * 2)).toFixed(2)}%)</tspan></text>
</svg>`)
})
.get('/', (_, res): void => {
.get('/', async (_, res): Promise<void> => {
res.send(`
<!DOCTYPE html>
@@ -347,9 +347,9 @@ export default express.Router()
<table class="serie">
${objectMap(setsData, (serie, serieId) => {
${(await Promise.all(objectMap(setsData, async (serie, serieId) => {
// Loop through every series and name them
const name = Serie.findOne('en', { id: serieId })?.name ?? Serie.findOne('ja' as any, { id: serieId })?.name
const name = (await findOneSerie('en', { id: serieId }))?.name ?? (await findOneSerie('ja' as any, { id: serieId }))?.name
return `
<thead>
<tr><th class="notop" colspan="35"><h2>${name} (${serieId})</h2></th></tr>
@@ -363,11 +363,11 @@ export default express.Router()
</tr>
</thead>
<tbody>
${objectMap(serie, (data, setId) => {
${(await Promise.all(objectMap(serie, async (data, setId) => {
// loop through every sets
// find the set in the first available language (Should be English globally)
const setTotal = Set.findOne(data[0] as 'en', { id: setId })
const setTotal = await findOneSet(data[0] as 'en', { id: setId })
let str = '<tr>' + `<td>${setTotal?.name} (${setId}) <br />${setTotal?.cardCount.total ?? 1} cards</td>`
// let str = '<tr>' + `<td>${setId})</td>`
@@ -400,9 +400,9 @@ export default express.Router()
// finish Row
return str + '</tr>'
}).join('')}
}))).join('')}
</tbody>
`}).join('')}
`}))).join('')}
</table>
</body>
</html>