diff --git a/.bruno/cards/Get All cards.bru b/.bruno/cards/Get All cards.bru new file mode 100644 index 0000000000..88456b8cf5 --- /dev/null +++ b/.bruno/cards/Get All cards.bru @@ -0,0 +1,15 @@ +meta { + name: Get All cards + type: http + seq: 6 +} + +get { + url: {{BASE_URL}}/v2/en/cards + body: none + auth: none +} + +assert { + res.status: eq 200 +} diff --git a/.bruno/endpoints/v2/lang/random/Random Set.bru b/.bruno/endpoints/v2/lang/random/Random Set.bru index 6ae999356f..18ea15cc01 100644 --- a/.bruno/endpoints/v2/lang/random/Random Set.bru +++ b/.bruno/endpoints/v2/lang/random/Random Set.bru @@ -17,5 +17,4 @@ params:query { assert { res.status: eq 200 res.body.name: contains Sword - res.responseTime: lte 30 } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 73666680fc..7ad52c3d08 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,10 +2,10 @@ name: Build Docker image on: push: - branches: + branches: - master pull_request: - branches: + branches: - "**" env: @@ -68,9 +68,7 @@ jobs: - name: Pre build server run: | bun install --frozen-lockfile - cd server - bun install --frozen-lockfile - bun run compile + bun scripts/compiler/index.ts rm -rf node_modules cd .. rm -rf node_modules diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8664739ed2..79413d8180 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,10 +2,13 @@ name: Test the Data on: push: - branches: + branches: - master pull_request_target: - branches: + branches: + - "**" + pull_request: + branches: - "**" permissions: {} @@ -31,14 +34,13 @@ jobs: run: | bun install -g @usebruno/cli bun install --frozen-lockfile - cd server - bun install --frozen-lockfile - bun run compile + bun scripts/compiler/index.ts - name: Validate the data & the server run: | bun run validate cd server + bun install --frozen-lockfile bun run --bun validate - name: Validate some requests diff --git a/Dockerfile b/Dockerfile index e006cbbe23..3a38c1bf6b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,20 +14,16 @@ ADD --chown=bun:bun package.json bun.lock ./ ADD --chown=bun:bun server/package.json server/bun.lock ./server/ # install dependencies -RUN bun install --frozen-lockfile && \ - cd server && \ - bun install --frozen-lockfile +RUN bun install --frozen-lockfile # Add project files ADD --chown=bun:bun . . # build -RUN cd server && \ - bun run compile +RUN bun scripts/compiler/all.ts -# remove dev dependencies (bun do not yet support "prune") +# Install productions packages RUN cd server && \ - rm -rf node_modules && \ bun install --frozen-install --production # go to another VM diff --git a/bun.lock b/bun.lock index 7defd98976..b35df9e8f2 100644 --- a/bun.lock +++ b/bun.lock @@ -10,6 +10,7 @@ "@types/node": "^20.0.0", "glob": "^10.0.0", "jscodeshift": "^17.3.0", + "openapi-typescript": "^7.10.1", "ts-node": "^10.0.0", "typescript": "^5.0.0", }, @@ -108,6 +109,12 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@redocly/ajv": ["@redocly/ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw=="], + + "@redocly/config": ["@redocly/config@0.22.2", "", {}, "sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ=="], + + "@redocly/openapi-core": ["@redocly/openapi-core@1.34.6", "", { "dependencies": { "@redocly/ajv": "^8.11.2", "@redocly/config": "^0.22.0", "colorette": "^1.2.0", "https-proxy-agent": "^7.0.5", "js-levenshtein": "^1.1.6", "js-yaml": "^4.1.0", "minimatch": "^5.0.1", "pluralize": "^8.0.0", "yaml-ast-parser": "0.0.43" } }, "sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw=="], + "@tsconfig/node10": ["@tsconfig/node10@1.0.11", "", {}, "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw=="], "@tsconfig/node12": ["@tsconfig/node12@1.0.11", "", {}, "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="], @@ -126,12 +133,18 @@ "acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], + "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], "ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], "arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -146,12 +159,16 @@ "caniuse-lite": ["caniuse-lite@1.0.30001731", "", {}, "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg=="], + "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], + "clone-deep": ["clone-deep@4.0.1", "", { "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", "shallow-clone": "^3.0.0" } }, "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "colorette": ["colorette@1.4.0", "", {}, "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="], + "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="], "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], @@ -174,6 +191,10 @@ "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "find-cache-dir": ["find-cache-dir@2.1.0", "", { "dependencies": { "commondir": "^1.0.1", "make-dir": "^2.0.0", "pkg-dir": "^3.0.0" } }, "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ=="], @@ -190,8 +211,12 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], @@ -204,12 +229,18 @@ "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + "js-levenshtein": ["js-levenshtein@1.1.6", "", {}, "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "jscodeshift": ["jscodeshift@17.3.0", "", { "dependencies": { "@babel/core": "^7.24.7", "@babel/parser": "^7.24.7", "@babel/plugin-transform-class-properties": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/preset-flow": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "@babel/register": "^7.24.6", "flow-parser": "0.*", "graceful-fs": "^4.2.4", "micromatch": "^4.0.7", "neo-async": "^2.5.0", "picocolors": "^1.0.1", "recast": "^0.23.11", "tmp": "^0.2.3", "write-file-atomic": "^5.0.1" }, "peerDependencies": { "@babel/preset-env": "^7.1.6" }, "optionalPeers": ["@babel/preset-env"], "bin": { "jscodeshift": "bin/jscodeshift.js" } }, "sha512-LjFrGOIORqXBU+jwfC9nbkjmQfFldtMIoS6d9z2LG/lkmyNXsJAySPT+2SWXJEoE68/bCWcxKpXH37npftgmow=="], "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], @@ -234,6 +265,8 @@ "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], + "openapi-typescript": ["openapi-typescript@7.10.1", "", { "dependencies": { "@redocly/openapi-core": "^1.34.5", "ansi-colors": "^4.1.3", "change-case": "^5.4.4", "parse-json": "^8.3.0", "supports-color": "^10.2.2", "yargs-parser": "^21.1.1" }, "peerDependencies": { "typescript": "^5.x" }, "bin": { "openapi-typescript": "bin/cli.js" } }, "sha512-rBcU8bjKGGZQT4K2ekSTY2Q5veOQbVG/lTKZ49DeCyT9z62hM2Vj/LLHjDHC9W7LJG8YMHcdXpRZDqC1ojB/lw=="], + "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], "p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="], @@ -242,6 +275,8 @@ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], + "path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -258,8 +293,12 @@ "pkg-dir": ["pkg-dir@3.0.0", "", { "dependencies": { "find-up": "^3.0.0" } }, "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw=="], + "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], + "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "shallow-clone": ["shallow-clone@3.0.1", "", { "dependencies": { "kind-of": "^6.0.2" } }, "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA=="], @@ -282,6 +321,8 @@ "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], "tmp": ["tmp@0.2.3", "", {}, "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w=="], @@ -292,6 +333,8 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], @@ -310,6 +353,10 @@ "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "yaml-ast-parser": ["yaml-ast-parser@0.0.43", "", {}, "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "yn": ["yn@3.1.1", "", {}, "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="], "@ampproject/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], @@ -320,6 +367,8 @@ "@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], + "@redocly/openapi-core/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + "make-dir/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], diff --git a/data/Mega Evolution/MEP Black Star Promos.ts b/data/Mega Evolution/MEP Black Star Promos.ts index db782249d9..8bc64b7532 100644 --- a/data/Mega Evolution/MEP Black Star Promos.ts +++ b/data/Mega Evolution/MEP Black Star Promos.ts @@ -8,6 +8,7 @@ const set: Set = { de: "MEP Black Star Promos", en: "MEP Black Star Promos", es: "MEP Black Star Promos", + "es-mx": "MEP Black Star Promos", fr: "MEP Black Star Promos", it: "MEP Black Star Promos", pt: "MEP Black Star Promos" diff --git a/data/Scarlet & Violet/Black Bolt.ts b/data/Scarlet & Violet/Black Bolt.ts index 22cc7e6c15..fb63c04554 100644 --- a/data/Scarlet & Violet/Black Bolt.ts +++ b/data/Scarlet & Violet/Black Bolt.ts @@ -8,6 +8,7 @@ const set: Set = { de: "Schwarze Blitze", en: "Black Bolt", es: "Fulgor Negro", + "es-mx": "Fulgor Negro", fr: "Foudre Noire", it: "Luce Nera", pt: "Raio Preto" diff --git a/data/Scarlet & Violet/White Flare.ts b/data/Scarlet & Violet/White Flare.ts index 090ca497c4..d0b6650f57 100644 --- a/data/Scarlet & Violet/White Flare.ts +++ b/data/Scarlet & Violet/White Flare.ts @@ -8,6 +8,7 @@ const set: Set = { de: "Weiße Flammen", en: "White Flare", es: "Llama Blanca", + "es-mx": "Llama Blanca" fr: "Flamme Blanche", it: "Fuoco Bianco", pt: "Fogo Branco" diff --git a/interfaces.d.ts b/interfaces.d.ts index 2a92a013e1..11f6cfb4dc 100644 --- a/interfaces.d.ts +++ b/interfaces.d.ts @@ -1,11 +1,13 @@ export type SupportedLanguages = -// inter languages + // inter languages 'en' | 'fr' | 'es' | 'es-mx' | 'it' | 'pt' | 'pt-br' | 'pt-pt' | 'de' | 'nl' | 'pl' | 'ru' | // Asian languages 'ja' | 'ko' | 'zh-tw' | 'id' | 'th' | 'zh-cn' export type Languages = Partial> +export type LanguagesV2 = Record + export interface Serie { id: string name: Languages @@ -31,9 +33,9 @@ interface variant_detailed { * and the Unlimited version of the set had no shadow. */ subtype?: 'shadowless' | 'unlimited' | '1999-2000-copyright' | 'missing-expansion-symbol' | 'gold-border' - | 'missing-hp' | 'aoki-error' | '1999-copyright' | 'evolution-box-error' | 'no-holo-error' | 'd-ink-dot-error' - | 'energy-symbol-error' | 'text-error' | 'shifted-energy-cost' | 'japanese-back' | 'no-e-reader' | 'rarity-error' - | 'cosmos' + | 'missing-hp' | 'aoki-error' | '1999-copyright' | 'evolution-box-error' | 'no-holo-error' | 'd-ink-dot-error' + | 'energy-symbol-error' | 'text-error' | 'shifted-energy-cost' | 'japanese-back' | 'no-e-reader' | 'rarity-error' + | 'cosmos' /** * define the size of the card @@ -159,11 +161,11 @@ export interface Set { } } -export interface Card { +export interface Card { /** * Card Name (Including the suffix if next to card name) */ - name: Languages + name: LanguagesV2 /** * Card illustrator @@ -213,16 +215,16 @@ export interface Card { * - Uncommon: https://www.tcgdex.net/database/Sword-&-Shield/Darkness-Ablaze/136-Furret */ rarity: 'ACE SPEC Rare' | 'Amazing Rare' | 'Classic Collection' | 'Common' | - 'Double rare' | 'Full Art Trainer' | 'Holo Rare' | 'Holo Rare V' | - 'Holo Rare VMAX' | 'Holo Rare VSTAR' | 'Hyper rare' | 'Illustration rare' | - 'LEGEND' | 'None' | 'Radiant Rare' | 'Rare' | 'Rare Holo' | 'Rare Holo LV.X' | - 'Rare PRIME' | 'Secret Rare' | 'Shiny Ultra Rare' | 'Shiny rare' | 'Shiny rare V' | - 'Shiny rare VMAX' | 'Special illustration rare' | 'Ultra Rare' | 'Uncommon' - // Black White rare - | 'Black White Rare' - | 'Mega Hyper Rare' - // Pokémon TCG Pocket Rarities - | 'One Diamond' | 'Two Diamond' | 'Three Diamond' | 'Four Diamond' | 'One Star' | 'Two Star' | 'Three Star' | 'Crown' | 'One Shiny' | 'Two Shiny' + 'Double rare' | 'Full Art Trainer' | 'Holo Rare' | 'Holo Rare V' | + 'Holo Rare VMAX' | 'Holo Rare VSTAR' | 'Hyper rare' | 'Illustration rare' | + 'LEGEND' | 'None' | 'Radiant Rare' | 'Rare' | 'Rare Holo' | 'Rare Holo LV.X' | + 'Rare PRIME' | 'Secret Rare' | 'Shiny Ultra Rare' | 'Shiny rare' | 'Shiny rare V' | + 'Shiny rare VMAX' | 'Special illustration rare' | 'Ultra Rare' | 'Uncommon' + // Black White rare + | 'Black White Rare' + | 'Mega Hyper Rare' + // Pokémon TCG Pocket Rarities + | 'One Diamond' | 'Two Diamond' | 'Three Diamond' | 'Four Diamond' | 'One Star' | 'Two Star' | 'Three Star' | 'Crown' | 'One Shiny' | 'Two Shiny' /** * Card Category @@ -272,7 +274,7 @@ export interface Card { /** * Pokemon Sub Evolution */ - evolveFrom?: Languages + evolveFrom?: LanguagesV2 /** * Pokemon Weight @@ -282,7 +284,7 @@ export interface Card { /** * Pokemon Description */ - description?: Languages + description?: LanguagesV2 /** * Level of the Pokemon @@ -326,8 +328,8 @@ export interface Card { * ex https://www.tcgdex.net/database/dp/dp2/75 */ item?: { - name: Languages - effect: Languages + name: LanguagesV2 + effect: LanguagesV2 } /** @@ -337,8 +339,8 @@ export interface Card { */ abilities?: Array<{ type: 'Pokemon Power' | 'Poke-BODY' | 'Poke-POWER' | 'Ability' | 'Ancient Trait' - name: Languages - effect: Languages + name: LanguagesV2 + effect: LanguagesV2 }> /** @@ -346,8 +348,8 @@ export interface Card { */ attacks?: Array<{ cost?: Array - name: Languages - effect?: Languages + name: LanguagesV2 + effect?: LanguagesV2 damage?: string | number }> @@ -367,21 +369,21 @@ export interface Card { retreat?: number //Trainer/Energy - effect?: Languages + effect?: LanguagesV2 // Trainer Only trainerType?: 'Supporter' | // https://www.tcgdex.net/database/ex/ex7/83 - 'Item' | // https://www.tcgdex.net/database/ex/ex7/89 - 'Stadium' | // https://www.tcgdex.net/database/ex/ex7/87 - 'Tool' | // https://www.tcgdex.net/database/neo/neo1/93 - 'Ace Spec' | // https://www.tcgdex.net/database/bw/bw7/139 - 'Technical Machine' | // https://www.tcgdex.net/database/ecard/ecard1/144 - 'Goldenrod Game Corner' | // https://www.tcgdex.net/database/neo/neo1/83 - 'Rocket\'s Secret Machine' // https://www.tcgdex.net/database/ex/ex7/84 + 'Item' | // https://www.tcgdex.net/database/ex/ex7/89 + 'Stadium' | // https://www.tcgdex.net/database/ex/ex7/87 + 'Tool' | // https://www.tcgdex.net/database/neo/neo1/93 + 'Ace Spec' | // https://www.tcgdex.net/database/bw/bw7/139 + 'Technical Machine' | // https://www.tcgdex.net/database/ecard/ecard1/144 + 'Goldenrod Game Corner' | // https://www.tcgdex.net/database/neo/neo1/83 + 'Rocket\'s Secret Machine' // https://www.tcgdex.net/database/ex/ex7/84 // Energy Only energyType?: 'Normal' | // https://www.tcgdex.net/database/ecard/ecard1/160 - 'Special' // https://www.tcgdex.net/database/ecard/ecard1/158 + 'Special' // https://www.tcgdex.net/database/ecard/ecard1/158 thirdParty?: { tcgplayer?: number diff --git a/meta/definitions/openapi.yaml b/meta/definitions/openapi.yaml index 9c84c04cbf..d063686bf6 100644 --- a/meta/definitions/openapi.yaml +++ b/meta/definitions/openapi.yaml @@ -1,4 +1,5 @@ -# OPEN-API API definition +# OpenAPI definition, this is the main way for the API to know what it returns & if it is working well +# This document also is used to generate GraphQL schema openapi: 3.1.0 info: title: TCGdex API @@ -14,7 +15,7 @@ info: See https://tcgdex.dev/rest/filtering-sorting-pagination for more details. You can find out more about TCGdex at - [https://www.tcgdex.net](https://www.tcgdex.net) or on + [https://tcgdex.dev](https://tcgdex.dev) or on [Discord](https://discord.gg/NehYTAhsZE). contact: name: TCGdex @@ -26,7 +27,7 @@ info: version: "2" externalDocs: description: Find out more about TCGdex - url: http://www.tcgdex.net/docs + url: https://tcgdex.dev servers: - url: https://api.tcgdex.net/v2/{lang} variables: @@ -60,24 +61,24 @@ paths: description: Returns a list of all cards, with support for filtering, sorting, and pagination operationId: cards parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': - description: 'Successful request' + "200": + description: "Successful request" content: application/json: schema: type: array items: - $ref: '#/components/schemas/CardResume' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/CardResume" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /cards/{cardId}: get: tags: @@ -93,18 +94,18 @@ paths: schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - $ref: '#/components/schemas/Card' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/Card" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /sets: get: tags: @@ -112,24 +113,24 @@ paths: summary: Get all sets description: Lists all Pokemon TCG sets with basic information, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: schema: type: array items: - $ref: '#/components/schemas/SetResume' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/SetResume" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /sets/{set}: get: tags: @@ -144,18 +145,18 @@ paths: schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - $ref: '#/components/schemas/Set' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/Set" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The Set doesn't exist /sets/{set}/{cardLocalId}: get: @@ -176,18 +177,18 @@ paths: schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - $ref: '#/components/schemas/Card' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/Card" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: the Set or cardLocalId are incorrect /series: get: @@ -196,24 +197,24 @@ paths: summary: Get all series description: Returns a list of all available Pokemon TCG series, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: schema: type: array items: - $ref: '#/components/schemas/SerieResume' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/SerieResume" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /series/{serie}: get: tags: @@ -228,18 +229,18 @@ paths: schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - $ref: '#/components/schemas/Serie' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/Serie" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The Serie doesn't exist /categories: get: @@ -248,12 +249,12 @@ paths: summary: Get all categories description: Returns a list of all available card categories, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: @@ -261,10 +262,10 @@ paths: type: array items: type: string - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /categories/{category}: get: tags: @@ -272,29 +273,29 @@ paths: summary: Get cards by category description: Returns cards matching the specified category, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" - name: category in: path required: true schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - $ref: '#/components/schemas/StringEndpoint' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/StringEndpoint" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The Category doesn't exist /hp: get: @@ -303,12 +304,12 @@ paths: summary: Get all HP values description: Returns a list of all available HP values for Pokemon cards, with support for sorting and pagination parameters: - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: @@ -316,10 +317,10 @@ paths: type: array items: type: string - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /hp/{hp}: get: tags: @@ -327,29 +328,29 @@ paths: summary: Get cards by HP value description: Returns cards with the specified HP value, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" - name: hp in: path required: true schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - $ref: '#/components/schemas/StringEndpoint' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/StringEndpoint" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The HP count doesn't exist /illustrators: get: @@ -358,12 +359,12 @@ paths: summary: Get all illustrators description: Returns a list of all card illustrators, with support for sorting and pagination parameters: - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: @@ -371,10 +372,10 @@ paths: type: array items: type: string - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /illustrators/{illustrator}: get: tags: @@ -382,29 +383,29 @@ paths: summary: Get cards by illustrator description: Returns cards by the specified illustrator, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" - name: illustrator in: path required: true schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - $ref: '#/components/schemas/StringEndpoint' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/StringEndpoint" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The Illustrator doesn't exist /rarities: get: @@ -413,12 +414,12 @@ paths: summary: Get all rarities description: Returns a list of all card rarity types, with support for sorting and pagination parameters: - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: @@ -426,10 +427,10 @@ paths: type: array items: type: string - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /rarities/{rarity}: get: tags: @@ -437,29 +438,29 @@ paths: summary: Get cards by rarity description: Returns cards with the specified rarity, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" - name: rarity in: path required: true schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - $ref: '#/components/schemas/StringEndpoint' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/StringEndpoint" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The Rarity doesn't exist /retreats: get: @@ -468,12 +469,12 @@ paths: summary: Get all retreat costs description: Returns a list of all available retreat cost values, with support for sorting and pagination parameters: - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: @@ -481,10 +482,10 @@ paths: type: array items: type: string - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /retreats/{retreat}: get: tags: @@ -492,29 +493,29 @@ paths: summary: Get cards by retreat cost description: Returns cards with the specified retreat cost, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" - name: retreat in: path required: true schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - $ref: '#/components/schemas/StringEndpoint' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/StringEndpoint" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The Retreat count doesn't exist /types: get: @@ -523,12 +524,12 @@ paths: summary: Get all types description: Returns a list of all Pokemon card types, with support for sorting and pagination parameters: - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: @@ -536,10 +537,10 @@ paths: type: array items: type: string - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /types/{type}: get: tags: @@ -547,31 +548,29 @@ paths: summary: Get cards by type description: Returns cards of the specified type, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" - name: type in: path required: true schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/CardResume' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/StringEndpoint" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The Type doesn't exist /dex-ids: get: @@ -580,12 +579,12 @@ paths: summary: Get all Pokedex IDs description: Returns a list of all Pokedex ID numbers, with support for sorting and pagination parameters: - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: @@ -593,10 +592,10 @@ paths: type: array items: type: string - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /dex-ids/{dexId}: get: tags: @@ -604,31 +603,37 @@ paths: summary: Get cards by Pokedex ID description: Returns cards with the specified Pokedex ID, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" - name: dexId in: path required: true schema: type: string responses: - '200': + "200": description: Successful request content: application/json: - schema: - type: array - items: - $ref: '#/components/schemas/CardResume' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + type: object + properties: + name: + type: number + description: The string value this endpoint represents + cards: + type: array + description: Cards matching the specific string value + items: + $ref: "#/components/schemas/CardResume" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: no cards contain the specified dexID /energy-types: get: @@ -637,12 +642,12 @@ paths: summary: Get all energy types description: Returns a list of all energy card types, with support for sorting and pagination parameters: - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: @@ -650,10 +655,10 @@ paths: type: array items: type: string - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /energy-types/{energy-type}: get: tags: @@ -661,32 +666,78 @@ paths: summary: Get cards by energy type description: Returns cards with the specified energy type, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" - name: energy-type in: path required: true schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/CardResume' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/StringEndpoint" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The specified energy-type doesn't exist + /random/card: + get: + summary: Returns a random card + parameters: + - $ref: "#/components/parameters/sortField" + responses: + "200": + description: Successful request + content: + application/json: + schema: + $ref: "#/components/schemas/Card" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" + /random/set: + get: + summary: Returns a random card + parameters: + - $ref: "#/components/parameters/sortField" + responses: + "200": + description: Successful request + content: + application/json: + schema: + $ref: "#/components/schemas/Set" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" + /random/serie: + get: + summary: Returns a random card + parameters: + - $ref: "#/components/parameters/sortField" + responses: + "200": + description: Successful request + content: + application/json: + schema: + $ref: "#/components/schemas/Serie" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /regulation-marks: get: tags: @@ -694,12 +745,12 @@ paths: summary: Get all regulation marks description: Returns a list of all regulation marks on cards, with support for sorting and pagination parameters: - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: @@ -707,10 +758,10 @@ paths: type: array items: type: string - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /regulation-marks/{regulation-mark}: get: tags: @@ -718,31 +769,29 @@ paths: summary: Get cards by regulation mark description: Returns cards with the specified regulation mark, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" - name: regulation-mark in: path required: true schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/CardResume' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/StringEndpoint" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The regulation-mark doesn't exist /stages: get: @@ -751,12 +800,12 @@ paths: summary: Get all Pokemon stages description: Returns a list of all Pokemon evolution stages, with support for sorting and pagination parameters: - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: @@ -764,10 +813,10 @@ paths: type: array items: type: string - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /stages/{stage}: get: tags: @@ -775,31 +824,29 @@ paths: summary: Get cards by stage description: Returns cards with the specified evolution stage, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" - name: stage in: path required: true schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/CardResume' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/StringEndpoint" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The stage doesn't exist /suffixes: get: @@ -808,12 +855,12 @@ paths: summary: Get all card suffixes description: Returns a list of all card suffixes (EX, GX, V, etc.), with support for sorting and pagination parameters: - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: @@ -821,10 +868,10 @@ paths: type: array items: type: string - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /suffixes/{suffix}: get: tags: @@ -832,31 +879,29 @@ paths: summary: Get cards by suffix description: Returns cards with the specified suffix, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" - name: suffix in: path required: true schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/CardResume' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/StringEndpoint" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The suffix doesn't exist /trainer-types: get: @@ -865,12 +910,12 @@ paths: summary: Get all trainer types description: Returns a list of all trainer card types, with support for sorting and pagination parameters: - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: @@ -878,10 +923,10 @@ paths: type: array items: type: string - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /trainer-types/{trainer-type}: get: tags: @@ -889,31 +934,29 @@ paths: summary: Get cards by trainer type description: Returns cards with the specified trainer type, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" - name: trainer-type in: path required: true schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/CardResume' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/StringEndpoint" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The trainer-type doesn't exist /variants: get: @@ -922,12 +965,12 @@ paths: summary: Get all card variants description: Returns a list of all card variant types (normal, holo, reverse, etc.), with support for sorting and pagination parameters: - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" responses: - '200': + "200": description: Successful request content: application/json: @@ -935,10 +978,10 @@ paths: type: array items: type: string - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /variants/{variant}: get: tags: @@ -946,31 +989,29 @@ paths: summary: Get cards by variant description: Returns cards with the specified variant, with support for filtering, sorting, and pagination parameters: - - $ref: '#/components/parameters/filter' - - $ref: '#/components/parameters/sortField' - - $ref: '#/components/parameters/sortOrder' - - $ref: '#/components/parameters/paginationPage' - - $ref: '#/components/parameters/paginationItemsPerPage' + - $ref: "#/components/parameters/filter" + - $ref: "#/components/parameters/sortField" + - $ref: "#/components/parameters/sortOrder" + - $ref: "#/components/parameters/paginationPage" + - $ref: "#/components/parameters/paginationItemsPerPage" - name: variant in: path required: true schema: type: string responses: - '200': + "200": description: Successful request content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/CardResume' - '404': - $ref: '#/components/responses/NotFound' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/StringEndpoint" + "404": + $ref: "#/components/responses/NotFound" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" description: The variant doesn't exist components: parameters: @@ -996,10 +1037,10 @@ components: - `notnull:` - Field is not null - Multiple values with `|` (e.g., `name=eq:Furret|Pikachu`) schema: - type: [object, null] + type: [object, "null"] additionalProperties: type: string - example: "{\"name\": \"eq:Furret\", \"hp\": \"lte:60\"}" + example: '{"name": "eq:Furret", "hp": "lte:60"}' sortField: name: "sort:field" @@ -1007,7 +1048,7 @@ components: description: Field to sort results by (overrides default sorting) required: false schema: - type: [string, null] + type: [string, "null"] example: "name" sortOrder: @@ -1016,7 +1057,7 @@ components: description: Order to sort results (ASC or DESC) required: false schema: - type: [string, null] + type: [string, "null"] enum: [ASC, DESC] default: null @@ -1026,7 +1067,7 @@ components: description: Page number for paginated results required: false schema: - type: [integer, null] + type: [integer, "null"] minimum: 1 default: 1 example: 1 @@ -1037,7 +1078,7 @@ components: description: Number of items per page (applied when pagination:page is used) required: false schema: - type: [integer, null] + type: [integer, "null"] minimum: 1 default: 100 example: 25 @@ -1047,7 +1088,7 @@ components: content: application/problem+json: schema: - $ref: '#/components/schemas/Problem' + $ref: "#/components/schemas/Problem" example: type: "https://tcgdex.dev/errors/not-found" title: "Resource Not Found" @@ -1060,7 +1101,7 @@ components: content: application/problem+json: schema: - $ref: '#/components/schemas/Problem' + $ref: "#/components/schemas/Problem" example: type: "https://tcgdex.dev/errors/bad-request" title: "Invalid Request" @@ -1073,7 +1114,7 @@ components: content: application/problem+json: schema: - $ref: '#/components/schemas/Problem' + $ref: "#/components/schemas/Problem" example: type: "https://tcgdex.dev/errors/server-error" title: "Internal Server Error" @@ -1094,29 +1135,35 @@ components: type: string format: uri description: URI reference that identifies the problem type - example: "https://tcgdex.dev/errors/not-found" + examples: + - "https://tcgdex.dev/errors/not-found" title: type: string description: Short, human-readable summary of the problem type - example: "The resource you are trying to reach does not exist" + examples: + - "The resource you are trying to reach does not exist" status: type: integer description: HTTP status code - example: 404 + examples: + - 404 detail: type: string description: Human-readable explanation specific to this occurrence of the problem - example: "The requested card with ID 'invalid-id' could not be found" + examples: + - "The requested card with ID 'invalid-id' could not be found" endpoint: type: string description: The API endpoint that was requested - example: "/v2/i-do-not-exists" + examples: + - "/v2/i-do-not-exists" method: type: string description: The HTTP method used for the request - example: "GET" + examples: + - "GET" examples: - notFound: + - notFound: value: type: "https://tcgdex.dev/errors/not-found" title: "Resource Not Found" @@ -1124,7 +1171,7 @@ components: detail: "The requested card with ID 'base1-999' could not be found" endpoint: "/v2/cards/base1-999" method: "GET" - badRequest: + - badRequest: value: type: "https://tcgdex.dev/errors/bad-request" title: "Invalid Request" @@ -1132,7 +1179,7 @@ components: detail: "The provided parameter 'hp' must be a valid number" endpoint: "/v2/hp/abc" method: "GET" - serverError: + - serverError: value: type: "https://tcgdex.dev/errors/server-error" title: "Internal Server Error" @@ -1140,22 +1187,19 @@ components: detail: "An unexpected error occurred while processing your request" endpoint: "/v2/cards" method: "GET" - WeakRes: - type: array - xml: - name: WeakResItem - wrapped: true - items: - required: - - type - type: object - properties: - type: - type: string - example: "Psychic" - value: - type: [string, null] - example: x2 + + WeaknessResistance: + type: object + properties: + type: + type: string + examples: + - "Psychic" + value: + type: [string, "null"] + examples: + - x2 + Booster: description: Represents a booster pack that contains Pokemon cards required: @@ -1167,38 +1211,44 @@ components: type: string description: The booster ID using format boo_- pattern: "^boo_.*-.*$" - example: "boo_A1-mewtwo" + examples: + - "boo_A1-mewtwo" name: type: string description: The name of the booster - example: "Mewtwo" + examples: + - "Mewtwo" logo: - type: [string, null] + type: [string, "null"] description: URL to the logo of the booster - example: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-logo" + examples: + - "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-logo" artwork_front: - type: [string, null] + type: [string, "null"] description: URL to the front artwork of the booster pack - example: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-front" + examples: + - "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-front" artwork_back: - type: [string, null] + type: [string, "null"] description: URL to the back artwork of the booster pack - example: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-back" + examples: + - "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-back" examples: - complete: + - complete: value: id: "boo_A1-mewtwo" name: "Mewtwo" logo: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-logo" artwork_front: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-front" artwork_back: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-front" - minimal: + - minimal: value: id: "boo_base1-base" name: "Base Set Booster" logo: null artwork_front: "https://assets.tcgdex.net/en/base/base1/boosters/base/front" artwork_back: null + SerieResume: description: Basic information about a Pokemon TCG series required: @@ -1213,172 +1263,34 @@ components: type: string description: Name of the series logo: - type: [string, null] + type: [string, "null"] description: URL to the logo of the series Serie: description: Detailed information about a Pokemon TCG series, including all sets within the series - required: - - id - - name - - sets - - firstSet - - lastSet - - releaseDate - type: object - properties: - id: - type: string - description: Unique identifier of the series - name: - type: string - description: Name of the series - logo: - type: string - description: URL to the logo of the series - releaseDate: - type: string - format: date - description: Release date of the series - firstSet: - $ref: '#/components/schemas/SetResume' - description: The first set released in this series - lastSet: - $ref: '#/components/schemas/SetResume' - description: The most recent set released in this series - sets: - type: array - description: All sets contained in this series - items: - $ref: '#/components/schemas/SetResume' - Set: - description: Detailed information about a Pokemon TCG set, including card lists and metadata - required: - - cardCount - - cards - - id - - name - - legal - - serie - - releaseDate - type: object - properties: - id: - type: string - description: Unique identifier of the set - example: "base1" - name: - type: string - description: Name of the set - example: "Base Set" - logo: - type: string - description: URL to the logo of the set - example: "https://assets.tcgdex.net/en/base/base1/logo" - symbol: - type: [string, null] - description: URL to the set symbol - example: "https://assets.tcgdex.net/univ/base/base1/symbol" - serie: - $ref: '#/components/schemas/SerieResume' - description: The series this set belongs to - tcgOnline: - type: [string, null] - description: Identifier for this set in Pokemon TCG Online - releaseDate: - type: string - format: date - description: Official release date of the set - variants: - type: object - description: Indicates which card variants exist in this set - properties: - normal: - type: [boolean, null] - description: Indicates whether normal variants exist - reverse: - type: [boolean, null] - description: Indicates whether reverse holo variants exist - holo: - type: [boolean, null] - description: Indicates whether holo variants exist - firstEdition: - type: [boolean, null] - description: Indicates whether first edition variants exist - wPromo: - type: [boolean, null] - description: Indicates whether promotional variants exist - boosters: - type: array - description: The booster packs available in this set - items: - $ref: '#/components/schemas/Booster' - example: - - id: "boo_A1-mewtwo" - name: "Mewtwo" - logo: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-logo" - artwork_front: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-front" - artwork_back: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-front" - - id: "boo_A1-pikachu" - name: "Pikachu" - legal: - type: object - description: Information about tournament legality of this set + allOf: + - $ref: '#/components/schemas/SerieResume' + - type: object required: - - standard - - expanded - properties: - standard: - type: boolean - description: Indicates whether this set is legal in standard format tournaments - example: false - expanded: - type: boolean - description: Indicates whether this set is legal in expanded format tournaments - example: true - abbreviation: - type: object - description: Common abbreviations used for this set + - sets + - firstSet + - lastSet + - releaseDate properties: - official: - type: string - description: The official abbreviation used by Pokemon Company - example: "BS" - localized: + releaseDate: type: string - description: Localized abbreviation used in the current language - example: BS - cardCount: - required: - - official - - total - type: object - description: Statistics about the number of cards in this set - properties: - total: - type: number - description: Total number of cards in the set including variants - example: 101 - official: - type: number - description: Number of cards officially numbered in the set - example: 100 - normal: - type: number - description: Number of cards having a normal variant - reverse: - type: number - description: Number of cards having a reverse holo variant - holo: - type: number - description: Number of cards having a holographic variant - firstEd: - type: [number, null] - description: Number of cards having a first edition variant - cards: - type: array - description: List of all cards in this set - items: - $ref: '#/components/schemas/CardResume' + format: date + description: Release date of the series + firstSet: + $ref: "#/components/schemas/SetResume" + description: The first set released in this series + lastSet: + $ref: "#/components/schemas/SetResume" + description: The most recent set released in this series + sets: + type: array + description: All sets contained in this series + items: + $ref: "#/components/schemas/SetResume" SetResume: description: Basic information about a Pokemon TCG set required: @@ -1390,19 +1302,23 @@ components: id: type: string description: Unique identifier of the set - example: base1 + examples: + - base1 name: type: string description: Name of the set - example: Base Set + examples: + - Base Set logo: type: string description: URL to the logo of the set - example: https://assets.tcgdex.net/en/base/base1/logo + examples: + - https://assets.tcgdex.net/en/base/base1/logo symbol: type: string description: URL to the set symbol - example: https://assets.tcgdex.net/univ/base/base1/symbol + examples: + - https://assets.tcgdex.net/univ/base/base1/symbol cardCount: required: - official @@ -1413,11 +1329,132 @@ components: total: type: number description: Total number of cards in the set including variants - example: 101 + examples: + - 101 official: type: number description: Number of cards officially numbered in the set - example: 100 + examples: + - 100 + Set: + allOf: + - $ref: '#/components/schemas/SetResume' + - type: object + description: Detailed information about a Pokemon TCG set, including card lists and metadata + required: + - cardCount + - cards + - legal + - serie + - releaseDate + properties: + serie: + $ref: "#/components/schemas/SerieResume" + description: The series this set belongs to + tcgOnline: + type: [string, "null"] + description: Identifier for this set in Pokemon TCG Online + releaseDate: + type: string + format: date + description: Official release date of the set + variants: + type: object + description: Indicates which card variants exist in this set + properties: + normal: + type: [boolean, "null"] + description: Indicates whether normal variants exist + reverse: + type: [boolean, "null"] + description: Indicates whether reverse holo variants exist + holo: + type: [boolean, "null"] + description: Indicates whether holo variants exist + firstEdition: + type: [boolean, "null"] + description: Indicates whether first edition variants exist + wPromo: + type: [boolean, "null"] + description: Indicates whether promotional variants exist + boosters: + type: array + description: The booster packs available in this set + items: + $ref: "#/components/schemas/Booster" + examples: + - id: "boo_A1-mewtwo" + name: "Mewtwo" + logo: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-logo" + artwork_front: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-front" + artwork_back: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-front" + - id: "boo_A1-pikachu" + name: "Pikachu" + legal: + type: object + description: Information about tournament legality of this set + required: + - standard + - expanded + properties: + standard: + type: boolean + description: Indicates whether this set is legal in standard format tournaments + examples: + - false + expanded: + type: boolean + description: Indicates whether this set is legal in expanded format tournaments + examples: + - true + abbreviation: + type: object + description: Common abbreviations used for this set + properties: + official: + type: string + description: The official abbreviation used by Pokemon Company + examples: + - "BS" + localized: + type: string + description: Localized abbreviation used in the current language + examples: + - BS + cardCount: + required: + - official + - total + type: object + description: Statistics about the number of cards in this set + properties: + total: + type: number + description: Total number of cards in the set including variants + examples: + - 101 + official: + type: number + description: Number of cards officially numbered in the set + examples: + - 100 + normal: + type: number + description: Number of cards having a normal variant + reverse: + type: number + description: Number of cards having a reverse holo variant + holo: + type: number + description: Number of cards having a holographic variant + firstEd: + type: [number, "null"] + description: Number of cards having a first edition variant + cards: + type: array + description: List of all cards in this set + items: + $ref: "#/components/schemas/CardResume" CardResume: description: Basic information about a Pokemon TCG card required: @@ -1429,265 +1466,402 @@ components: id: type: string description: Unique identifier of the card - example: "base1-1" + examples: + - "base1-1" localId: type: string description: Card number within its set - example: "1" + examples: + - "1" image: - type: [string, null] + type: [string, "null"] description: URL to the card image - example: "https://assets.tcgdex.net/en/base/base1/1" + examples: + - "https://assets.tcgdex.net/en/base/base1/1" name: type: string description: Name of the card (including any suffix) - example: "Alakazam" + examples: + - "Alakazam" Card: description: Detailed information about a Pokemon TCG card, including game mechanics - required: - - category - - id - - localId - - name - - rarity - - set - - legal - - updated - type: object - properties: - id: - type: string - description: Unique identifier of the card - example: base1-1 - localId: - type: string - description: Card number within its set - example: '1' - image: - type: string - description: URL to the card image - example: https://assets.tcgdex.net/en/base/base1/1 - name: - type: string - description: Name of the card (including any suffix) - example: Alakazam - illustrator: - type: [string, null] - description: Artist who illustrated the card - example: "Ken Sugimori" - category: - type: string - description: Card category (Pokemon, Trainer, or Energy) - example: "Pokemon" - rarity: - type: string - description: Card rarity (Common, Uncommon, Rare, etc.) - example: "Rare" - set: - $ref: '#/components/schemas/SetResume' - description: The set this card belongs to - variants: + allOf: + - $ref: "#/components/schemas/CardResume" + - type: object required: - - firstEdition - - holo - - normal - - reverse - - wPromo - type: object - description: Indicates which variants of this card exist (overrides set variants) + - category + - rarity + - set + - legal + - updated properties: - normal: - type: boolean - description: Indicates whether a normal variant exists - reverse: - type: boolean - description: Indicates whether a reverse holo variant exists - holo: - type: boolean - description: Indicates whether a holo variant exists - firstEdition: - type: boolean - description: Indicates whether a first edition variant exists - wPromo: - type: boolean - description: Indicates whether a promotional variant exists - variant_detailed: - type: [array,"null"] - description: Detailed information about a card variant - items: + pricing: + type: object + description: Pricing information for the card + properties: + tcgplayer: + type: object + properties: + updated: + type: string + description: Date and time when the pricing information was last updated + examples: + - "2023-03-15T12:34:56Z" + unit: + type: string + description: Currency unit of the price + examples: + - "USD" + normal: + type: object + properties: + lowPrice: + type: number + description: Lowest price for the card + examples: + - 1.99 + midPrice: + type: number + description: Middle price for the card + examples: + - 2.99 + highPrice: + type: number + description: Highest price for the card + examples: + - 3.99 + marketPrice: + type: number + description: Market price for the card + examples: + - 2.50 + directLowPrice: + type: number + description: Direct low price for the card + examples: + - 2.00 + reverse: + type: object + properties: + lowPrice: + type: number + description: Lowest price for the card + examples: + - 1.99 + midPrice: + type: number + description: Middle price for the card + examples: + - 2.99 + highPrice: + type: number + description: Highest price for the card + examples: + - 3.99 + marketPrice: + type: number + description: Market price for the card + examples: + - 2.50 + directLowPrice: + type: number + description: Direct low price for the card + examples: + - 2.00 + cardmarket: + type: object + description: Cardmarket pricing information + properties: + updated: + type: string + description: Date and time when the pricing information was last updated + examples: + - "2023-03-15T12:34:56Z" + unit: + type: string + description: Currency unit of the price + examples: + - "EUR" + avg: + type: number + description: Average price of the card + examples: + - 1.23 + low: + type: number + description: Lowest price of the card + examples: + - 0.50 + trend: + type: number + description: Trend of the card price + examples: + - 0.10 + avg1: + type: number + description: Average price of the card in the last 1 day + examples: + - 1.23 + avg7: + type: number + description: Average price of the card in the last 7 days + examples: + - 1.23 + avg30: + type: number + description: Average price of the card in the last 30 days + examples: + - 1.23 + avg-holo: + type: number + description: Average price of the card in the last 30 days, including holographic cards + examples: + - 1.23 + trend-holo: + type: number + description: Trend of the card's price in the last 30 days, including holographic cards + examples: + - 1.23 + avg1-holo: + type: number + description: Average price of the card in the last 1 day, including holographic cards + examples: + - 1.23 + avg7-holo: + type: number + description: Average price of the card in the last 7 days, including holographic cards + examples: + - 1.23 + avg30-holo: + type: number + description: Average price of the card in the last 30 days, including holographic cards + examples: + - 1.23 + effect: + type: [string, "null"] + description: Card Effect (mostly for trainers & energies) + illustrator: + type: [string, "null"] + description: Artist who illustrated the card + examples: + - "Ken Sugimori" + category: + type: string + description: Card category (Pokemon, Trainer, or Energy) + examples: + - "Pokemon" + rarity: + type: string + description: Card rarity (Common, Uncommon, Rare, etc.) + examples: + - "Rare" + set: + $ref: "#/components/schemas/SetResume" + description: The set this card belongs to + variants: + required: + - firstEdition + - holo + - normal + - reverse + - wPromo + type: object + description: Indicates which variants of this card exist (overrides set variants) + properties: + normal: + type: boolean + description: Indicates whether a normal variant exists + reverse: + type: boolean + description: Indicates whether a reverse holo variant exists + holo: + type: boolean + description: Indicates whether a holo variant exists + firstEdition: + type: boolean + description: Indicates whether a first edition variant exists + wPromo: + type: boolean + description: Indicates whether a promotional variant exists + variant_detailed: + type: [array, "null"] + description: Detailed information about a card variant + items: type: object required: - - type + - type properties: - type: - type: string - description: The type of variant (e.g., normal, reverse, holo, etc.) - size: - type: string - description: The size of the variant (e.g., standard, jumbo, etc.) - nullable: true - stamp: - type: array - description: The stamps of the variant (e.g., 'Staff', 'Pokemon-Centre', etc.) - items: - type: string - nullable: true - foil: - type: string - description: The foil of the variant (e.g., 'Pokeball', 'MasterBall', etc.) - nullable: true - hp: - type: [number, "null"] - description: Hit Points (HP) of the Pokemon - example: 80 - types: - type: [array, null] - description: Energy types of the Pokemon - items: - type: string - example: Psychic - evolveFrom: - type: [string, null] - description: Name of the Pokemon this evolves from - example: "Kadabra" - stage: - type: [string, null] - description: Evolution stage (Basic, Stage 1, Stage 2, etc.) - example: "Stage 2" - suffix: - type: [string, null] - description: Special card suffix (EX, GX, V, etc.) - item: - required: - - effect - - name - type: object - description: Pokemon's held item information - properties: - name: - type: string - description: Name of the held item - effect: - type: string - description: Effect of the held item - trainerType: - type: [string, null] - description: Type of trainer card (Item, Supporter, Stadium, Tool) - energyType: - type: [string, null] - description: Type of energy card (Basic, Special) - regulationMark: - type: [string, null] - description: Regulation mark on cards (introduced in Sword & Shield) - example: "E" - legal: - type: object - description: Information about tournament legality of this card - properties: - standard: - type: boolean - description: Indicates whether this card is legal in standard format tournaments - example: false - expanded: - type: boolean - description: Indicates whether this card is legal in expanded format tournaments - example: true - description: - type: [string, null] - description: Descriptive text or flavor text on the card - level: - type: [number, string] - description: Level of the Pokemon (can be a string 'X' for LEVEL-UP cards) - example: 42 - abilities: - type: array - description: Pokemon abilities (Poké-Power, Poké-Body, Ability, etc.) - items: - type: object - properties: - type: - type: string - description: Type of ability (Poké-Power, Poké-Body, Ability, etc.) - example: "Poké-Power" - name: + type: + type: string + description: The type of variant (e.g., normal, reverse, holo, etc.) + size: + type: [string, "null"] + description: The size of the variant (e.g., standard, jumbo, etc.) + stamp: + type: [array, "null"] + description: The stamps of the variant (e.g., 'Staff', 'Pokemon-Centre', etc.) + items: + type: string + foil: + type: [string, "null"] + description: The foil of the variant (e.g., 'Pokeball', 'MasterBall', etc.) + hp: + type: [number, "null"] + description: Hit Points (HP) of the Pokemon + examples: + - 80 + types: + type: [array, "null"] + description: Energy types of the Pokemon + items: type: string - description: Name of the ability - example: "Damage Swap" - effect: - type: [string, null] - description: Effect text of the ability - example: "As often as you like..." - attacks: - type: array - description: Pokemon attacks - items: - required: - - name - type: object - properties: - cost: - type: array - description: Energy cost to use this attack - items: + examples: + - Psychic + evolveFrom: + type: [string, "null"] + description: Name of the Pokemon this evolves from + examples: + - "Kadabra" + stage: + type: [string, "null"] + description: Evolution stage (Basic, Stage 1, Stage 2, etc.) + examples: + - "Stage 2" + suffix: + type: [string, "null"] + description: Special card suffix (EX, GX, V, etc.) + item: + required: + - effect + - name + type: object + description: Pokemon's held item information + properties: + name: type: string - example: "Psychic" - name: - type: string - description: Name of the attack - example: "Confuse Ray" - effect: - type: [string, null] - description: Effect text of the attack - example: "Flip a coin. If heads, ..." - damage: - type: [number, string] - description: Base damage of the attack (can be string for special damage) - example: 30 - retreat: - type: [number, null] - description: Retreat cost of the Pokemon - example: 3 - dexId: - type: array - description: Pokedex number(s) of the Pokemon - xml: - name: dexIdItem - wrapped: true - items: - type: number - weaknesses: - type: array - description: Types the Pokemon is weak against - xml: - name: weakness - wrapped: true - items: - $ref: '#/components/schemas/WeakRes' - resistances: - type: array - description: Types the Pokemon is resistant to - xml: - name: resistance - wrapped: true - items: - $ref: '#/components/schemas/WeakRes' - boosters: - type: array - description: Boosters in which this card is available - items: - $ref: '#/components/schemas/Booster' - example: - - id: "boo_A1-mewtwo" - name: "Mewtwo" - logo: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-logo" - artwork_front: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-front" - artwork_back: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-front" - updated: - type: string - description: Timestamp of when this card data was last updated - format: date-time - # xml: - # name: Card + description: Name of the held item + effect: + type: string + description: Effect of the held item + trainerType: + type: [string, "null"] + description: Type of trainer card (Item, Supporter, Stadium, Tool) + energyType: + type: [string, "null"] + description: Type of energy card (Basic, Special) + regulationMark: + type: [string, "null"] + description: Regulation mark on cards (introduced in Sword & Shield) + examples: + - "E" + legal: + type: object + description: Information about tournament legality of this card + properties: + standard: + type: boolean + description: Indicates whether this card is legal in standard format tournaments + examples: + - false + expanded: + type: boolean + description: Indicates whether this card is legal in expanded format tournaments + examples: + - true + description: + type: [string, "null"] + description: Descriptive text or flavor text on the card + level: + type: [number, string] + description: Level of the Pokemon (can be a string 'X' for LEVEL-UP cards) + examples: + - 42 + abilities: + type: array + description: Pokemon abilities (Poké-Power, Poké-Body, Ability, etc.) + items: + type: object + properties: + type: + type: string + description: Type of ability (Poké-Power, Poké-Body, Ability, etc.) + examples: + - "Poké-Power" + name: + type: string + description: Name of the ability + examples: + - "Damage Swap" + effect: + type: [string, "null"] + description: Effect text of the ability + examples: + - "As often as you like..." + attacks: + type: array + description: Pokemon attacks + items: + required: + - name + type: object + properties: + cost: + type: array + description: Energy cost to use this attack + items: + type: string + examples: + - "Psychic" + name: + type: string + description: Name of the attack + examples: + - "Confuse Ray" + effect: + type: [string, "null"] + description: Effect text of the attack + examples: + - "Flip a coin. If heads, ..." + damage: + type: [number, string] + description: Base damage of the attack (can be string for special damage) + examples: + - 30 + retreat: + type: [number, "null"] + description: Retreat cost of the Pokemon + examples: + - 3 + dexId: + type: array + description: Pokedex number(s) of the Pokemon + items: + type: number + weaknesses: + type: array + description: Types the Pokemon is weak against + items: + $ref: "#/components/schemas/WeaknessResistance" + resistances: + type: array + description: Types the Pokemon is resistant to + items: + $ref: "#/components/schemas/WeaknessResistance" + boosters: + type: array + description: Boosters in which this card is available + items: + $ref: "#/components/schemas/Booster" + examples: + - id: "boo_A1-mewtwo" + name: "Mewtwo" + logo: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-logo" + artwork_front: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-front" + artwork_back: "https://assets.tcgdex.net/en/tcgp/A1/boosters/mewtwo-front" + updated: + type: string + description: Timestamp of when this card data was last updated + format: date-time StringEndpoint: description: Represents a collection of cards matching a specific string value (for endpoints like types, rarities, etc.) required: @@ -1702,4 +1876,4 @@ components: type: array description: Cards matching the specific string value items: - $ref: '#/components/schemas/CardResume' + $ref: "#/components/schemas/CardResume" diff --git a/package.json b/package.json index b9ec2112dd..b4bc3e37e7 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@types/node": "^20.0.0", "glob": "^10.0.0", "jscodeshift": "^17.3.0", + "openapi-typescript": "^7.10.1", "ts-node": "^10.0.0", "typescript": "^5.0.0" } diff --git a/scripts/compiler/cards.ts b/scripts/compiler/cards.ts new file mode 100644 index 0000000000..b70a5156f0 --- /dev/null +++ b/scripts/compiler/cards.ts @@ -0,0 +1,106 @@ +import { objectClean, objectKeys } from '@dzeio/object-util' +import Queue from '@dzeio/queue' +import { globSync } from 'glob' +import fs from 'node:fs' +import path from 'node:path' +import { Card as DBCard, Serie as DBSerie, Set as DBSet, Languages, SupportedLanguages } from '../../interfaces' +import { extractCached } from '../utils/ts-extract-utils' +import { translate, validateLanguages } from './libs/translation' +import { getAsset } from './providers/assets' +import { getLastEdit } from './providers/git' + +import { CompiledCard } from './interfaces' +import { cardIsLegal } from './libs/legalUtils' +import { getHashs } from './providers/assets' + +await getHashs() + +// +// const files = globSync('data/*/*/*.ts') +const files = globSync('{data,data-asia}/*/*/*.ts') + +let counter = 0 +let lastPrint = 0 +function addToCounter() { + counter++ + if (counter >= lastPrint + 1000) { + lastPrint = counter + console.log(`Processed ${Math.round((counter / files.length) * 100)}% (${counter}/${files.length}) cards`) + } +} + +const queue = new Queue(500, 20) +queue.start() + +const out: Array = [] +for (const file of files) { + await queue.add((async () => { + const setPath = file.slice(0, file.lastIndexOf(path.sep)) + '.ts' + const card: DBCard = await extractCached(file) + const localId = decodeURIComponent(file.slice(file.lastIndexOf(path.sep) + 1, file.lastIndexOf('.'))) + const set: DBSet = await extractCached(setPath) + const seriePath = setPath.slice(0, setPath.lastIndexOf(path.sep)) + '.ts' + const serie: DBSerie = await extractCached(seriePath) + + const langs = objectKeys(card.name) + const id = `${set.id}-${localId}` + + const res: CompiledCard = { + ...card, + id: id, + image: await getAsset(langs, serie.id, set.id, localId), + localId: localId, + rarity: translate('rarity', card.rarity ?? 'None', langs), + category: translate('category', card.category, langs), + types: card.types?.map((type) => translate('types', type, langs)), + abilities: card.abilities?.map((ability) => ({ + ...ability, + name: validateLanguages(ability.name, langs), + effect: validateLanguages(ability.effect, langs), + type: translate('abilityType', ability.type, langs) + })), + attacks: card.attacks?.map((attack) => ({ + ...attack, + name: validateLanguages(attack.name, langs), + effect: validateLanguages(attack.effect, langs), + cost: attack.cost?.map((type) => translate('types', type, langs)) + })), + weaknesses: card.weaknesses?.map((it) => ({ + ...it, + type: translate('types', it.type, langs) + })), + resistances: card.weaknesses?.map((it) => ({ + ...it, + type: translate('types', it.type, langs) + })), + stage: card.stage ? translate('stage', card.stage, langs) : undefined, + suffix: card.suffix ? translate('suffix', card.suffix, langs) : undefined, + trainerType: card.trainerType ? translate('trainerType', card.trainerType, langs) : undefined, + energyType: card.energyType ? translate('energyType', card.energyType, langs) : undefined, + set: set.id, + legal: { + standard: cardIsLegal('standard', card, id), + expanded: cardIsLegal('expanded', card, id) + }, + updated: await getLastEdit(file) + } + + // remove undefined + objectClean(res) + + out.push(res) + })() + .catch(() => { + console.warn("error processing card :( skipping...") + void addToCounter() + }) + .finally(() => { + void addToCounter() + })) +} +await queue.waitEnd() + + +await fs.promises.mkdir('./server/generated', { recursive: true }) +await fs.promises.writeFile('./server/generated/cards.json', JSON.stringify(out)) +console.log(`Done processing cards`) diff --git a/scripts/compiler/index.ts b/scripts/compiler/index.ts new file mode 100644 index 0000000000..6a7b17fa22 --- /dev/null +++ b/scripts/compiler/index.ts @@ -0,0 +1,23 @@ +import fs from 'fs/promises' +import openapiTS, { astToString } from 'openapi-typescript' + +console.log('1. Compiling OpenAPI spec') +const ast = await openapiTS(await fs.readFile('./meta/definitions/openapi.yaml', 'utf-8')) +const contents = astToString(ast) + +// (optional) write to file +await fs.writeFile("./server/src/openapi.ts", contents) + +console.log('2.1. Compiling cards') +await import('./cards') +console.log('2.2. Compiling sets') +await import('./sets') +console.log('2.3. Compiling series') +await import('./series') +await import('./stats') + +console.log('3. Moving public files') +// Finally copy definitions files to the public folder :D +for await (const file of await fs.readdir('./meta/definitions')) { + await fs.copyFile('./meta/definitions/' + file, './server/public/v2/' + file) +} diff --git a/scripts/compiler/interfaces.d.ts b/scripts/compiler/interfaces.d.ts new file mode 100644 index 0000000000..5b94f189ce --- /dev/null +++ b/scripts/compiler/interfaces.d.ts @@ -0,0 +1,61 @@ +import { Card as DBCard, Set as DBSet, Serie as DBSerie, Languages, SupportedLanguages } from '../../interfaces' + +export type Override = Omit & New + +export type CompiledCard = Override + stage?: Languages + suffix?: Languages + set: string + abilities?: Array[number], { + type: Languages + }>> + attacks?: Array[number], { + cost?: Array + }>> + weaknesses?: Array[number], { + type: Languages + }>> + resistances?: Array[number], { + type: Languages + }>> + trainerType?: Languages + energyType?: Languages + legal: { + standard: boolean + expanded: boolean + } + + updated: string +}> + +export type CompiledSet = Override + releaseDate: Languages + legal: { + expanded: boolean + standard: boolean + } + boosters: Array<{ + id: string + name: Languages + }> | undefined +}> + +export type CompiledSerie = Override + sets: Array +}> diff --git a/scripts/compiler/libs/legalUtils.ts b/scripts/compiler/libs/legalUtils.ts new file mode 100644 index 0000000000..b52a0b067b --- /dev/null +++ b/scripts/compiler/libs/legalUtils.ts @@ -0,0 +1,43 @@ +import * as legals from '../../../meta/legals' + +/** + * Check if a card is currently Legal + * @param type the type of legality + * @param card the card to check + * @param localId the card localid + * @returns {boolean} if the card is currently in the legal type + */ +export function cardIsLegal(type: 'standard' | 'expanded', card: { regulationMark?: string, energyType?: string, types?: Array }, id: string): boolean { + const legal = legals[type] + if ( + legal.includes.series.find((it) => id.startsWith(id)) || + legal.includes.sets.find((it) => id.startsWith(id)) || + card.energyType === "Normal" || + card.regulationMark && legal.includes.regulationMark.includes(card.regulationMark) + ) { + return !( + legal.excludes.sets.find((it) => id.startsWith(id)) || + (type === 'standard' && card.types?.includes("Fairy")) || + legal.excludes.cards.includes(id) + ) + } + + return false; +} + +/** + * Check if a set is currently Legal + * @param type the type of legality + * @param set the set to check + * @returns {boolean} if the set is currently in the legal type + */ +export function setIsLegal(type: 'standard' | 'expanded', set: { id: string; serie: { id: string } }): boolean { + const legal = legals[type] + if ( + legal.includes.series.includes(set.serie.id) || + legal.includes.sets.includes(set.id) + ) { + return !legal.excludes.sets.includes(set.id) + } + return false +} diff --git a/scripts/compiler/libs/osUtils.ts b/scripts/compiler/libs/osUtils.ts new file mode 100644 index 0000000000..4766417065 --- /dev/null +++ b/scripts/compiler/libs/osUtils.ts @@ -0,0 +1,40 @@ +import { exec, spawn } from 'node:child_process' + +/** + * run a command on the OS, it uses Spawn by default because exec seems to have a bug linked to the Buffer + * + * @param command the command to run + * @param useSpawn select the method to use to run the command + * @returns a string with the stdout + */ +export function runCommand(command: string, useSpawn = true): Promise { + if (!useSpawn) { + return new Promise((res, rej) => { + exec(command, (err, out) => { + if (err) { + rej(err) + } + res(out) + }) + }) + } + const splitted = command.split(' ') + command = splitted.shift()! + + return new Promise((res, rej) => { + const cmd = spawn(command, splitted) + let out: string = '' + cmd.stdout.on('data', (data) => { + out += data.toString() + }) + + cmd.on('close', (code) => { + if (code !== 0) { + console.log(`command exited with code ${code}`); + rej(code) + return + } + res(out) + }) + }) +} diff --git a/scripts/compiler/libs/translation.ts b/scripts/compiler/libs/translation.ts new file mode 100644 index 0000000000..fb433aed82 --- /dev/null +++ b/scripts/compiler/libs/translation.ts @@ -0,0 +1,58 @@ +import { Languages, SupportedLanguages } from '../../../interfaces' +import es from '../../../meta/translations/es.json' +import it from '../../../meta/translations/it.json' +import pt from '../../../meta/translations/pt.json' +import de from '../../../meta/translations/de.json' +import fr from '../../../meta/translations/fr.json' + +type translatable = 'types' | 'rarity' | 'stage' | 'category' | 'suffix' | 'abilityType' | 'trainerType' | 'energyType' + +const translations: Record>> = { + es, + fr, + it, + pt, + de +} + +export function translate(item: translatable, key: string, langs: Array): Languages { + + const res: Languages = {} + + for (const lang of langs) { + // Temporary trenslations are in english while they are being worked on + res[lang] = translations[lang]?.[item]?.[key] ?? key + if (!res[lang]) { + throw new Error(`Could not find translation for ${lang}.${item}.${key}`) + } + } + + + return res +} + +export function validateLanguages(key: T, langs: Array): T { + if (!key) { + return key + } + + // tmp skip validation + // for (const lang of langs) { + // if (!key[lang]) { + // throw new Error('invalid') + // } + // } + + return key +} + +export function normalizeLanguages(pot: Languages | string, langs: Array): Languages { + if (typeof pot === 'object') { + return pot + } + const out: Languages = {} + for (const lang of langs) { + out[lang] = pot + } + return out +} diff --git a/scripts/compiler/providers/assets.ts b/scripts/compiler/providers/assets.ts new file mode 100644 index 0000000000..f73d2bbae3 --- /dev/null +++ b/scripts/compiler/providers/assets.ts @@ -0,0 +1,33 @@ +import { objectGet } from "@dzeio/object-util" +import { Card, Languages, SupportedLanguages } from "../../../interfaces" + +const REMOTE = 'https://assets.tcgdex.net/datas.json' + +let dlCache: any = undefined + +export async function getHashs(): Promise { + if (!dlCache) { + console.log('fetching assets') + dlCache = await fetch(REMOTE).then((res) => res.json()) + } + return dlCache +} + +export async function getAsset(langs: Array, ...path: Array): Promise +export async function getAsset(lang: SupportedLanguages, ...path: Array): Promise +export async function getAsset(langs: SupportedLanguages | Array, ...path: Array): Promise { + if (typeof langs === 'string') { + const hashs = await getHashs() + const exists = Boolean(objectGet(hashs, [langs, ...path])) + if (!exists) { + return undefined + } + return `https://assets.tcgdex.net/${langs}/${path.join('/')}` + } + + const out: Languages = {} + for (const lang of langs) { + out[lang] = await getAsset(lang, ...path) + } + return out +} diff --git a/scripts/compiler/providers/git.ts b/scripts/compiler/providers/git.ts new file mode 100644 index 0000000000..db896b5d65 --- /dev/null +++ b/scripts/compiler/providers/git.ts @@ -0,0 +1,5 @@ +import { runCommand } from "../libs/osUtils" + +export async function getLastEdit(path: string): Promise { + return runCommand(`git log -1 --pretty="format:%cd" --date=iso-strict "${path}"`, false) +} diff --git a/scripts/compiler/series.ts b/scripts/compiler/series.ts new file mode 100644 index 0000000000..f16bb9b493 --- /dev/null +++ b/scripts/compiler/series.ts @@ -0,0 +1,81 @@ +import { objectClean, objectKeys } from '@dzeio/object-util' +import Queue from '@dzeio/queue' +import { globSync } from 'glob' +import fs from 'node:fs' +import { Serie as DBSerie, Set as DBSet, Languages } from '../../interfaces' +import { extractCached } from '../utils/ts-extract-utils' +import { CompiledSerie } from './interfaces' +import { translate } from './libs/translation' +import { getHashs } from './providers/assets' +import { getLastEdit } from './providers/git' + +await getHashs() + +const files = globSync('{data,data-asia}/*.ts') + +let counter = 0 +let lastPrint = 0 +function addToCounter() { + counter++ + if (counter >= lastPrint + 5) { + lastPrint = counter + console.log(`Processed ${Math.round((counter / files.length) * 100)}% (${counter}/${files.length}) series`) + } +} + +const queue = new Queue(5, 5) +queue.start() + +const out: Array = [] +for (const file of files) { + await queue.add((async () => { + // const setPath = file.slice(0, file.lastIndexOf('/')) + '.ts' + const serie: DBSerie = await extractCached(file) + if (!serie.id || serie.id === 'null') { + console.log('skipping serie, missing id') + return + } + // const localId = file.slice(file.lastIndexOf('/') + 1, file.lastIndexOf('.')) + // const serie: DBSerie = await extractCached(setPath) + const setsList = globSync(`{data,data-asia}/{${serie.name.en},${serie.id}}/*.ts`) + + const langs = objectKeys(serie.name) + + const sets = await Promise.all( + setsList.map((setPath) => extractCached(setPath) as Promise) + ) + + if (sets.length === 0) { + return + } + + const firstSet = sets.reduce((c, p) => c.releaseDate < p.releaseDate ? c : p) + + const res: CompiledSerie = { + ...serie, + sets: sets.map((it) => it.id), + firstSet: firstSet.id, + lastSet: sets.reduce((c, p) => c.releaseDate > p.releaseDate ? c : p).id, + releaseDate: firstSet.releaseDate, + energies: serie.energies?.map((it) => translate('types', it, langs)), + // releaseDate: normalizeLanguages(serie.releaseDate, langs), + updated: await getLastEdit(file) + } + + // remove undefined + objectClean(res) + + out.push(res) + })().catch((it) => { + console.error('error processing', file) + throw it + }).finally(() => { + void addToCounter() + })) +} +await queue.waitEnd() + + +await fs.promises.mkdir('./server/generated', { recursive: true }) +await fs.promises.writeFile('./server/generated/series.json', JSON.stringify(out)) +console.log(`Done processing series`) diff --git a/scripts/compiler/sets.ts b/scripts/compiler/sets.ts new file mode 100644 index 0000000000..1d0b21288c --- /dev/null +++ b/scripts/compiler/sets.ts @@ -0,0 +1,75 @@ +import { Set, Serie as DBSerie } from '../../interfaces' +import { globSync } from 'glob' +import { extractCached } from '../utils/ts-extract-utils' +import fs from 'node:fs' +import { objectClean, objectKeys, objectMap } from '@dzeio/object-util' +import { getLastEdit } from './providers/git' +import Queue from '@dzeio/queue' +import { normalizeLanguages } from './libs/translation' +import { getAsset, getHashs } from './providers/assets' +import { CompiledSet } from './interfaces' +import { setIsLegal } from './libs/legalUtils' +import path from 'node:path' + +await getHashs() +type DBSet = Set + +const files = globSync('{data,data-asia}/*/*.ts') + +let counter = 0 +let lastPrint = 0 +function addToCounter() { + counter++ + if (counter >= lastPrint + 100) { + lastPrint = counter + console.log(`Processed ${Math.round((counter / files.length) * 100)}% (${counter}/${files.length}) sets`) + } +} + +const queue = new Queue(50, 5) +queue.start() + +const out: Array = [] +for (const file of files) { + await queue.add((async () => { + const setPath = file.slice(0, file.lastIndexOf(path.sep)) + '.ts' + const set: DBSet = await extractCached(file) + const localId = file.slice(file.lastIndexOf(path.sep) + 1, file.lastIndexOf('.')) + const serie: DBSerie = await extractCached(setPath) + const cards = globSync(`{data,data-asia}/{${serie.name.en},${serie.id}}/{${set.name.en},${set.id}}/*.ts`) + + const langs = objectKeys(set.name) + + const res: CompiledSet = { + ...set, + serie: serie.id, + logo: await getAsset(langs, serie.id, set.id, 'logo'), + symbol: await getAsset('univ' as 'en', serie.id, set.id, 'symbol'), + cards: cards.map((it) => set.id + '-' + it.slice(it.lastIndexOf(path.sep) + 1, it.lastIndexOf('.'))), + releaseDate: normalizeLanguages(set.releaseDate, langs), + legal: { + expanded: setIsLegal('expanded', set), + standard: setIsLegal('standard', set) + }, + boosters: set.boosters ? objectMap(set.boosters, (booster, id) => ({ + id: `boo_${set.id}-${id}`, + name: normalizeLanguages(booster.name, langs), + // images will be coming soon... + })) : undefined, + updated: await getLastEdit(file) + } + + // remove undefined + objectClean(res) + + out.push(res) + })().finally(() => { + void addToCounter() + })) +} +await queue.waitEnd() + + +await fs.promises.mkdir('./server/generated', { recursive: true }) +await fs.promises.writeFile('./server/generated/sets.json', JSON.stringify(out)) +console.log(`Done processing sets`) diff --git a/scripts/compiler/stats.ts b/scripts/compiler/stats.ts new file mode 100644 index 0000000000..839e7cba77 --- /dev/null +++ b/scripts/compiler/stats.ts @@ -0,0 +1,100 @@ +import { objectRemap } from '@dzeio/object-util' +import { StatsFs } from 'node:fs' +import fs from 'node:fs/promises' +import cardsDB from '../../server/generated/cards.json' +import seriesDB from '../../server/generated/series.json' +import setsDB from '../../server/generated/sets.json' + +type Stats = Record + > +}> + +const langs = { + // calculate en & ja first + 'en': 'English', + 'ja': 'Japanese', + + // the others + 'zh-cn': 'Chinese (simplified)', + 'zh-tw': 'Chinese (traditionnal)', + 'nl': 'Dutch', + 'fr': 'French', + 'de': 'German', + 'id': 'Indonesian', + 'it': 'Italian', + 'ko': 'Korean', + 'pl': 'Polish', + 'pt': 'Portuguese (Brazil)', + // 'pt-br': 'Portuguese (brazil)', + 'pt-pt': 'Portuguese (Portugal)', + 'ru': 'Russian', + 'es': 'Spanish', + 'th': 'Thai', + 'es-mx': 'Spanish (Latin America)' +} as const + +const maxSets: Record = objectRemap(setsDB, (set, _) => { + return { + key: set.id, + value: set.cardCount.official > set.cards.length ? set.cardCount.official : set.cards.length + } +}) + +console.log(maxSets) + + +const out: Stats = {} +console.log('calculating statistics for the project status page') +for (const lang of Object.keys(langs)) { + console.log('processing language', lang) + const stats: Partial = {} + const cardsInLang = (cardsDB as Array).filter((it) => it.name[lang]) + stats.count = cardsInLang.length + stats.total = 0 + stats.images = cardsInLang.filter((it) => it.image[lang]).length + // console.log(cardsInLang) + + const series = seriesDB.filter((it) => it.name[lang]) + stats.sets = objectRemap(series, (serie, _) => { + console.log('processing serie', serie.id) + const sets = setsDB.filter((it) => it.name[lang] && it.serie === serie.id) + return { + key: serie.id as string, + value: objectRemap(sets, (set, _) => { + console.log('processing set', set.id) + stats.total += maxSets[set.id] ?? 0 + return { + key: set.id, + value: { + name: set.name[lang], + count: set.cards.length, + images: cardsInLang.filter((it) => it.id && set.cards.includes(it.id)).length + } + } + }) + } + }) + out[lang] = stats as Stats[string] +} + +// calculate totals +for (const lang of Object.keys(langs)) { + +} + +await fs.mkdir('./server/generated', { recursive: true }) +await fs.writeFile('./server/generated/stats.json', JSON.stringify(out)) +console.log(`Done processing cards`) diff --git a/server/.gitignore b/server/.gitignore index b36d0e8307..afb829ae01 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -5,3 +5,4 @@ /public/**/api.d.ts /public/**/openapi.yaml .env +/src/openapi.ts diff --git a/server/README.md b/server/README.md index f544001c7b..3a14363e0f 100644 --- a/server/README.md +++ b/server/README.md @@ -48,3 +48,13 @@ you can add environment variables to add features to the server: ### Using Docker Go to the parent directory and build the Dockerfile! + +## Build configs + +```bash +# generate ts config from openapi +npx openapi-typescript ./public/v2/openapi.yaml -o ./src/openapi.ts + +# Generate GraphQL from ts config +bun a.ts test.ts Card > out.gql +``` diff --git a/server/a.ts b/server/a.ts new file mode 100644 index 0000000000..b4eca5294c --- /dev/null +++ b/server/a.ts @@ -0,0 +1,122 @@ +#!/usr/bin/env node +import ts from 'typescript' +import fs from 'fs' +import path from 'path' + +const BUILTIN_FIELDS = new Set([ + 'valueOf', 'toString', 'hasOwnProperty', 'toLocaleString', + 'isPrototypeOf', 'propertyIsEnumerable', '__proto__' +]) + +const PRIMITIVE_FLAGS = + ts.TypeFlags.String + | ts.TypeFlags.Number + | ts.TypeFlags.Boolean + | ts.TypeFlags.BigInt + | ts.TypeFlags.Enum + | ts.TypeFlags.EnumLiteral + +function getJsDocComment(sym: ts.Symbol) { + // Try @description tag first + const jsDocs = sym.getJsDocTags() + const descriptionTag = jsDocs.find(t => t.name === 'description') + if (descriptionTag && descriptionTag.text) { + // console.log(descriptionTag.text) + return `"""${descriptionTag.text.map((it) => it.text).join('\n')}"""` + } + + // fallback: full JSDoc comment + const commentParts = sym.getDocumentationComment(ts.createProgram([], {}).getTypeChecker()) + if (commentParts && commentParts.length) { + const text = commentParts.map(c => c.text).filter(Boolean).join(' ') + if (text) return `"""${text}"""` + } + + return null +} + + +/** + * Convert TypeScript type to GraphQL type string + */ +function tsTypeToGql(type, checker, depth = 0, nameHint: string | undefined = undefined, defs: Array = [], seen = new Set()) { + if (type.flags & PRIMITIVE_FLAGS) { + if (type.flags & ts.TypeFlags.String) return 'String' + if (type.flags & ts.TypeFlags.Number) return 'Float' + if (type.flags & ts.TypeFlags.Boolean) return 'Boolean' + return 'String' + } + + if (type.symbol?.name === 'Array' && type.typeArguments?.length) { + return `[${tsTypeToGql(type.typeArguments[0], checker, depth, nameHint, defs, seen)}]` + } + + const props = type.getProperties() + if (props.length > 0) { + const realProps = props.filter(p => !BUILTIN_FIELDS.has(p.name)) + if (realProps.length === 0) return 'String' + + const subName = (nameHint ? nameHint[0].toUpperCase() + nameHint.slice(1) : 'Anonymous') + 'Sub' + depth + if (!seen.has(subName)) { + seen.add(subName) + defs.push(buildGraphQLFromType(subName, type, checker, defs, depth + 1, seen)) + } + return subName + } + + return 'String' +} + +/** + * Build GraphQL type definition string from TypeScript type + */ +function buildGraphQLFromType(name: string, type: ts.Type, checker: ts.TypeChecker, defs: Array, depth = 0, seen = new Set()) { + const props = type.getProperties().filter(p => !BUILTIN_FIELDS.has(p.name)) + const fields = props.map(sym => { + const decl = sym.valueDeclaration || sym.declarations?.[0] + const t = checker.getTypeOfSymbolAtLocation(sym, decl as ts.Declaration) + const gqlType = tsTypeToGql(t, checker, depth + 1, sym.name, defs, seen) + const optional = (sym.flags & ts.SymbolFlags.Optional) !== 0 + + const comment = getJsDocComment(sym) + return `${comment ? '\t' + comment + '\n' : ''}\t${sym.name}: ${gqlType}${optional ? '' : '!'}` + }) + return `type ${name} {\n${fields.join('\n\n')}\n}` +} + +/** + * Main function + */ +function main(file, targetTypeName) { + const program = ts.createProgram([file], { skipLibCheck: true }) + const checker = program.getTypeChecker() + const source = program.getSourceFile(file) + const defs: Array = [] + const seen = new Set() + + ts.forEachChild(source as ts.SourceFile, node => { + if ((ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) && + node.name.text === targetTypeName) { + const type = checker.getTypeAtLocation(node) + defs.push(buildGraphQLFromType(targetTypeName, type, checker, defs, 0, seen)) + } + }) + + if (defs.length === 0) { + console.error(`Type '${targetTypeName}' not found in ${file}`) + process.exit(1) + } + + console.log(defs.reverse().join('\n\n')) +} + +/** + * CLI + */ +const [,, file, typeName] = process.argv +if (!file || !typeName) { + console.error('Usage: node ts-to-gql-with-comments.js ') + process.exit(1) +} + +main(path.resolve(file), typeName) diff --git a/server/bun.lock b/server/bun.lock index b852e40ba5..8e9464c2f4 100644 --- a/server/bun.lock +++ b/server/bun.lock @@ -25,11 +25,16 @@ "@types/swagger-ui-express": "^4.1.8", "@typescript/native-preview": "^7.0.0-dev.20250805.1", "glob": "^10.4.5", + "openapi-typescript": "^7.10.1", "typescript": "^4.9.5", }, }, }, "packages": { + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + "@cachex/core": ["@cachex/core@1.0.1", "", { "dependencies": { "@dzeio/object-util": "^1.8.3" } }, "sha512-sHynwjF9hKIvwg8rTUdvA7MOUMcUC5Mq0dpynPBILRS+IPvsHcE4Cb2uRSs0/I2nxO7NQp9p+xHYistdfJJSwg=="], "@cachex/memory": ["@cachex/memory@1.0.1", "", { "dependencies": { "@cachex/core": "^1" } }, "sha512-KWUTdCCXhIlAkJaVZMhUh9kD0uq8PxC3Z34Q3lMGZOAV6FXP/cOMW89ALrWX3VkoRrrM4R6MIMO+amZNOvEqgw=="], @@ -120,6 +125,12 @@ "@prisma/instrumentation": ["@prisma/instrumentation@5.22.0", "", { "dependencies": { "@opentelemetry/api": "^1.8", "@opentelemetry/instrumentation": "^0.49 || ^0.50 || ^0.51 || ^0.52.0 || ^0.53.0", "@opentelemetry/sdk-trace-base": "^1.22" } }, "sha512-LxccF392NN37ISGxIurUljZSh1YWnphO34V5a0+T7FVQG2u9bhAXRTJpgmQ3483woVhkraQZFF7cbRrpbw/F4Q=="], + "@redocly/ajv": ["@redocly/ajv@8.11.3", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js-replace": "^1.0.1" } }, "sha512-4P3iZse91TkBiY+Dx5DUgxQ9GXkVJf++cmI0MOyLDxV9b5MUBI4II6ES8zA5JCbO72nKAJxWrw4PUPW+YP3ZDQ=="], + + "@redocly/config": ["@redocly/config@0.22.2", "", {}, "sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ=="], + + "@redocly/openapi-core": ["@redocly/openapi-core@1.34.5", "", { "dependencies": { "@redocly/ajv": "^8.11.2", "@redocly/config": "^0.22.0", "colorette": "^1.2.0", "https-proxy-agent": "^7.0.5", "js-levenshtein": "^1.1.6", "js-yaml": "^4.1.0", "minimatch": "^5.0.1", "pluralize": "^8.0.0", "yaml-ast-parser": "0.0.43" } }, "sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA=="], + "@scarf/scarf": ["@scarf/scarf@1.4.0", "", {}, "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ=="], "@sentry/core": ["@sentry/core@8.55.0", "", {}, "sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA=="], @@ -196,12 +207,18 @@ "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], + "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "apicache": ["apicache@1.6.3", "", {}, "sha512-jS3VfUFpQ9BesFQZcdd1vVYg3ZsO2kGPmTJHqycIYPAQs54r74CRiyj8DuzJpwzLwIfCBYzh4dy9Jt8xYbo27w=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="], "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -220,6 +237,8 @@ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], + "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], @@ -228,6 +247,8 @@ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "colorette": ["colorette@1.4.0", "", {}, "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="], + "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -286,6 +307,8 @@ "express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + "finalhandler": ["finalhandler@1.3.1", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" } }, "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ=="], "follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="], @@ -342,10 +365,14 @@ "http-proxy": ["http-proxy@1.18.1", "", { "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" } }, "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], "import-in-the-middle": ["import-in-the-middle@1.13.1", "", { "dependencies": { "acorn": "^8.14.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^1.2.2", "module-details-from-path": "^1.0.3" } }, "sha512-k2V9wNm9B+ysuelDTHjI9d5KPc4l8zAZTGqj+pcynvWkypZd857ryzN8jNC7Pg2YZXNMJcHRPpaDyCBbNyVRpA=="], + "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], @@ -378,6 +405,14 @@ "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + "js-levenshtein": ["js-levenshtein@1.1.6", "", {}, "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "limit-it": ["limit-it@3.2.11", "", { "dependencies": { "typpy": "^2.0.0" } }, "sha512-VdLa1lZYZnzT98oLMeCDl6Lwd9cEYIMQlPg34qL6CYuA+yQKoG7K12tfgI5K6bRC51kRM8v1UX67IhpNsnvo3A=="], "lowercase-keys": ["lowercase-keys@1.0.1", "", {}, "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA=="], @@ -430,6 +465,8 @@ "one-by-one": ["one-by-one@3.2.9", "", { "dependencies": { "obj-def": "^1.0.0", "sliced": "^1.0.1" } }, "sha512-H10TAq02LKrkSRTQz1mgvcKb64rRajZ+B5HWHBvkGigYNCPqL0Q/tLIN3vfha/DqZxXeKNfyCmgfEYo2hgFQgA=="], + "openapi-typescript": ["openapi-typescript@7.10.1", "", { "dependencies": { "@redocly/openapi-core": "^1.34.5", "ansi-colors": "^4.1.3", "change-case": "^5.4.4", "parse-json": "^8.3.0", "supports-color": "^10.2.2", "yargs-parser": "^21.1.1" }, "peerDependencies": { "typescript": "^5.x" }, "bin": { "openapi-typescript": "bin/cli.js" } }, "sha512-rBcU8bjKGGZQT4K2ekSTY2Q5veOQbVG/lTKZ49DeCyT9z62hM2Vj/LLHjDHC9W7LJG8YMHcdXpRZDqC1ojB/lw=="], + "os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="], "package-json": ["package-json@2.4.0", "", { "dependencies": { "got": "^5.0.0", "registry-auth-token": "^3.0.1", "registry-url": "^3.0.3", "semver": "^5.1.0" } }, "sha512-PRg65iXMTt/uK8Rfh5zvzkUbfAPitF17YaCY+IbHsYgksiLvtzWWTUildHth3mVaZ7871OJ7gtP4LBRBlmAdXg=="], @@ -440,7 +477,7 @@ "package.json": ["package.json@2.0.1", "", { "dependencies": { "git-package-json": "^1.4.0", "git-source": "^1.1.0", "package-json": "^2.3.1" } }, "sha512-pSxZ6XR5yEawRN2ekxx9IKgPN5uNAYco7MCPxtBEWMKO3UKWa1X2CtQMzMgloeGj2g2o6cue3Sb5iPkByIJqlw=="], - "parse-json": ["parse-json@2.2.0", "", { "dependencies": { "error-ex": "^1.2.0" } }, "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ=="], + "parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], "parse-url": ["parse-url@1.3.11", "", { "dependencies": { "is-ssh": "^1.3.0", "protocols": "^1.4.0" } }, "sha512-1wj9nkgH/5EboDxLwaTMGJh3oH3f+Gue+aGdh631oCqoSBpokzmMmOldvOeBPtB8GJBYJbaF93KPzlkU+Y1ksg=="], @@ -460,10 +497,14 @@ "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + "pinkie": ["pinkie@2.0.4", "", {}, "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg=="], "pinkie-promise": ["pinkie-promise@2.0.1", "", { "dependencies": { "pinkie": "^2.0.0" } }, "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw=="], + "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], "postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="], @@ -502,6 +543,8 @@ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "require-in-the-middle": ["require-in-the-middle@7.5.2", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3", "resolve": "^1.22.8" } }, "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ=="], "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="], @@ -562,7 +605,7 @@ "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], @@ -580,6 +623,8 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], "typescript": ["typescript@4.9.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g=="], @@ -596,6 +641,8 @@ "unzip-response": ["unzip-response@1.0.2", "", {}, "sha512-pwCcjjhEcpW45JZIySExBHYv5Y9EeL2OIGEfrSKp2dMUFGFv4CpvZkwJbVge8OvGH2BNNtJBx67DuKuJhf+N5Q=="], + "uri-js-replace": ["uri-js-replace@1.0.1", "", {}, "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g=="], + "url-parse-lax": ["url-parse-lax@1.0.0", "", { "dependencies": { "prepend-http": "^1.0.1" } }, "sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], @@ -624,6 +671,8 @@ "yaml": ["yaml@2.7.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="], + "yaml-ast-parser": ["yaml-ast-parser@0.0.43", "", {}, "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A=="], + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], @@ -644,12 +693,20 @@ "@prisma/instrumentation/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.53.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.53.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "semver": "^7.5.2", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A=="], + "@redocly/openapi-core/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + + "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "got/parse-json": ["parse-json@2.2.0", "", { "dependencies": { "error-ex": "^1.2.0" } }, "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ=="], + "graphile-config/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "https-proxy-agent/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "normalize-package-data/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], "package-json/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], @@ -690,6 +747,8 @@ "graphile-config/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "https-proxy-agent/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "require-in-the-middle/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], diff --git a/server/compiler/compilerInterfaces.d.ts b/server/compiler/compilerInterfaces.d.ts deleted file mode 100644 index 16a7fa6bec..0000000000 --- a/server/compiler/compilerInterfaces.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { SupportedLanguages } from '../../interfaces' - -export type FileFunction = (lang: SupportedLanguages) => Promise diff --git a/server/compiler/endpoints/cards.ts b/server/compiler/endpoints/cards.ts deleted file mode 100644 index 6a925c4f76..0000000000 --- a/server/compiler/endpoints/cards.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SupportedLanguages } from '../../../interfaces' -import { FileFunction } from '../compilerInterfaces' -import { cardToCardSingle, getCards } from '../utils/cardUtil' - -const fn: FileFunction = async (lang: SupportedLanguages) => { - const common = await getCards(lang) - return await Promise.all(common.map((card) => cardToCardSingle(card[0], card[1], lang).catch((e) => { - console.error('error compiling card', `${card[1].set.id}-${card[0]}`, e) - throw e - }))) -} - -export default fn diff --git a/server/compiler/endpoints/series.ts b/server/compiler/endpoints/series.ts deleted file mode 100644 index 7c65c0f916..0000000000 --- a/server/compiler/endpoints/series.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { SupportedLanguages } from '../../../interfaces' -import { FileFunction } from '../compilerInterfaces' -import { getSeries, serieToSerieSingle } from '../utils/serieUtil' - -const fn: FileFunction = async (lang: SupportedLanguages) => { - const common = await getSeries(lang) - return await Promise.all(common.map((val) => serieToSerieSingle(val, lang))) -} - -export default fn diff --git a/server/compiler/endpoints/sets.ts b/server/compiler/endpoints/sets.ts deleted file mode 100644 index 21ba3ed97d..0000000000 --- a/server/compiler/endpoints/sets.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { getSets, setToSetSingle } from '../utils/setUtil' -import { SupportedLanguages } from '../../../interfaces' -import { FileFunction } from '../compilerInterfaces' - - -const fn: FileFunction = async (lang: SupportedLanguages) => { - const common = await getSets(undefined, lang) - return await Promise.all(common.map((set) => setToSetSingle(set, lang))) -} - -export default fn diff --git a/server/compiler/endpoints/stats.ts b/server/compiler/endpoints/stats.ts deleted file mode 100644 index c299a9d434..0000000000 --- a/server/compiler/endpoints/stats.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { SupportedLanguages } from '../../../interfaces' -import { Set } from '../../../meta/definitions/api' -import { FileFunction } from '../compilerInterfaces' -import { getCards } from '../utils/cardUtil' -import { getSeries } from '../utils/serieUtil' -import { getSets, setToSetSingle } from '../utils/setUtil' - -interface Stats { - count: number - total: number - images: number - sets: Record> -} - -const fn: FileFunction = async (lang: SupportedLanguages) => { - const stats: Partial = {} - stats.count = (await getCards(lang)).length - // temporary fix until a better solution is found - const referenceLang = ['ja', 'ko', 'zh-tw', 'id', 'th', 'zh-cn'].includes(lang) ? 'ja' : 'en' - - const langSets = await Promise.all(await getSets(undefined, lang).then((sets) => sets.map(async (set) => await setToSetSingle(set, lang)))) - const englishSets = await Promise.all(await getSets(undefined, referenceLang).then((sets) => sets.map(async (set) => await setToSetSingle(set, referenceLang)))) - function max(lSet?: Set, refSet?: Set) { - if (!lSet) return refSet!.cardCount.total - if (!refSet) return lSet.cardCount.total - if (lSet.cardCount.total > refSet.cardCount.total) { - return lSet.cardCount.total - } - return refSet.cardCount.total - } - stats.total = langSets.reduce((p, set) => p + max(set, englishSets.find((s) => set.id === s.id)), 0) - stats.images = langSets.reduce((p1, set) => p1 + (set.cards.reduce((p2, card) => p2 + (card.image ? 1 : 0), 0)), 0) - stats.sets = {} - - const series = await getSeries(lang) - - for (const serie of series) { - stats.sets[serie.id] = {} - for (const set of langSets.filter((set) => set.serie.id === serie.id)) { - stats.sets[serie.id][set.id] = { - name: set.name, - count: set.cards.length, - images: set.cards.reduce((p, card) => p + (card.image ? 1 : 0), 0) - } - } - } - - - //const sts = await Promise.all(sets.map((set) => getCards(lang, set))) - return stats -} - -export default fn diff --git a/server/compiler/index.ts b/server/compiler/index.ts deleted file mode 100644 index 582d190542..0000000000 --- a/server/compiler/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* eslint-disable max-statements */ -import { existsSync, promises as fs } from 'fs' -import { SupportedLanguages } from '../../interfaces' -import { FileFunction } from './compilerInterfaces' -import { fetchRemoteFile, loadLastEdits } from './utils/util' - -const LANGS: Array = [ - 'en', 'fr', 'es', 'es-mx', 'it', 'pt', 'pt-br', 'pt-pt', 'de', 'nl', 'pl', 'ru', - 'ja', 'ko', 'zh-tw', 'id', 'th', 'zh-cn' -] - -const DIST_FOLDER = './generated' - -;(async () => { - const paths = (await fs.readdir('./compiler/endpoints')).filter((p) => p.endsWith('.ts')) - - // Prefetch the pictures at the start as it can bug because of bad connection - console.log('1. Loading remote sources') - await fetchRemoteFile('https://assets.tcgdex.net/datas.json') - - // Delete dist folder to be sure to have a clean base - try { - await fs.rm(DIST_FOLDER, {recursive: true}) - } catch {} - - console.log('\n2. Loading informations from GIT') - await loadLastEdits() - - console.log('\n3. Compiling Files') - - // Process each languages - let progressIndex = 0 - for await (const lang of LANGS) { - // loop through """endpoints""" - for await (const file of paths) { - - // final folder path - const folder = `${DIST_FOLDER}/${lang}` - - // console.log('files1:', await fs.readdir('.')) - // console.log('files2:', await fs.readdir(DIST_FOLDER)) - // console.log('files3:', await fs.readdir(folder)) - - // Make the folder - try { - await fs.mkdir(folder, { recursive: true }) - } catch { - // idk why it throws when file is present even if nodejs says it should not throw... - // maybe Bun changed how the throws works... - } - - // Import the """Endpoint""" - const fn = (await import(`./endpoints/${file}`)).default as FileFunction - - // Run the function - console.log(' ', 'Compiling', lang, file) - const item = await fn(lang) - - // Write to file - await fs.writeFile(`${folder}/${file.replace('.ts', '')}.json`, JSON.stringify( - item - )) - - console.log(`${(++progressIndex / (LANGS.length * paths.length) * 100).toFixed(2).padStart(5, '0')}%`, 'Compiled ', lang, file) - } - } - - console.log('4. Copying static files to public folder') - // Finally copy definitions files to the public folder :D - for await (const file of await fs.readdir('../meta/definitions')) { - await fs.copyFile('../meta/definitions/' + file, './public/v2/' + file) - } - -})() diff --git a/server/compiler/utils/cardUtil.ts b/server/compiler/utils/cardUtil.ts deleted file mode 100644 index 50a8e942ba..0000000000 --- a/server/compiler/utils/cardUtil.ts +++ /dev/null @@ -1,254 +0,0 @@ -/* eslint-disable sort-keys */ -import pathLib from 'node:path' -import { Card, Set, SupportedLanguages, Types } from '../../../interfaces' -import { CardResume, Card as CardSingle } from '../../../meta/definitions/api' -import { getSet, setToSetSimple } from './setUtil' -import translate from './translationUtil' -import { DB_PATH, cardIsLegal, fetchRemoteFile, getDataFolder, getLastEdit, resolveText, smartGlob } from './util' -import { objectMap, objectPick } from '@dzeio/object-util' -import { variant_detailed } from "../../public/v2/api"; - -export async function getCardPictures(cardId: string, card: Card, lang: SupportedLanguages): Promise { - try { - const file = await fetchRemoteFile('https://assets.tcgdex.net/datas.json') - const fileExists = Boolean(file[lang]?.[card.set.serie.id]?.[card.set.id]?.[cardId]) - if (fileExists) { - return `https://assets.tcgdex.net/${lang}/${card.set.serie.id}/${card.set.id}/${cardId}` - } - } catch { - return undefined - } - return undefined -} - -export async function cardToCardSimple(id: string, card: Card, lang: SupportedLanguages): Promise { - const cardName = resolveText(card.name, lang) - if (!cardName) { - throw new Error(`Card (${card.set.id}-${id}) has no name in (${lang})`) - } - const img = await getCardPictures(id, card, lang) - return { - id: `${card.set.id}-${id}`, - image: img, - localId: id, - name: cardName - } -} - -function variantsDetailedToVariants(variants_detailed: Array): CardSingle['variants'] { - return { - firstEdition: variants_detailed?.some((variant) => variant.stamp?.some((stamp) => stamp === '1st-edition')) ?? false, - holo: variants_detailed?.some((variant) => variant.type === 'holo') ?? false, - normal: variants_detailed?.some((variant) => variant.type === 'normal') ?? false, - reverse: variants_detailed?.some((variant) => variant.type === 'reverse') ?? false, - wPromo: variants_detailed?.some((variant) => variant.stamp?.some((stamp) => stamp === 'w-Promo')) ?? false - } -} - -function variantsToVariantsDetailed(variants: CardSingle['variants'],lang: SupportedLanguages): Array { - const result: Array = []; - const addVariant = (type: string, stamps: string[] = []) => { - result.push({ - type, - size: translate('variantSize', "standard", lang) as any, - stamp: stamps.length > 0 ? stamps : undefined - }); - }; - - if (typeof variants?.normal === 'boolean' ? variants.normal : true) { - addVariant('normal'); - if (variants?.firstEdition) addVariant('normal', ['1st-edition']); - if (variants?.wPromo) addVariant('normal', ['w-Promo']); - } - if (typeof variants?.reverse === 'boolean' ? variants.reverse : true) { - addVariant('reverse'); - if (variants?.firstEdition) addVariant('reverse', ['1st-edition']); - } - if (typeof variants?.holo === 'boolean' ? variants.holo : true) { - addVariant('holo'); - if (variants?.firstEdition) addVariant('holo', ['1st-edition']); - } - - return result.length > 0 ? result : undefined; -} - -// eslint-disable-next-line max-lines-per-function -export async function cardToCardSingle(localId: string, card: Card, lang: SupportedLanguages): Promise { - const image = await getCardPictures(localId, card, lang) - - if (!card.name[lang]) { - throw new Error(`Card (${localId}) dont exist in (${lang})`) - } - - return { - category: translate('category', card.category, lang) as any, - id: `${card.set.id}-${localId}`, - illustrator: card.illustrator, - image, - localId, - name: resolveText(card.name, lang) as string, - - rarity: translate('rarity', card.rarity, lang) as any, - set: await setToSetSimple(card.set, lang), - - variants : Array.isArray(card.variants) ? - variantsDetailedToVariants(card.variants) : { - firstEdition: typeof card.variants?.firstEdition === 'boolean' ? card.variants.firstEdition : false, - holo: typeof card.variants?.holo === 'boolean' ? card.variants.holo : true, - normal: typeof card.variants?.normal === 'boolean' ? card.variants.normal : true, - reverse: typeof card.variants?.reverse === 'boolean' ? card.variants.reverse : true, - wPromo: typeof card.variants?.wPromo === 'boolean' ? card.variants.wPromo : false - }, - - variants_detailed: Array.isArray(card.variants) ? card.variants?.map((variant) => { - return { - type: translate('variantType', variant.type, lang) as any, - subtype: translate('variantSubtype', variant.subtype, lang) as any, - // only include size when it's not standard - size: variant.size && variant.size !== 'standard' ? translate('variantSize', variant.size, lang) as any : translate('variantSize', "standard", lang) as any, - stamp: variant.stamp ? variant.stamp.map((stamp) => { - return translate('variantStamp', stamp, lang) - }) : undefined, - foil: variant.foil ? translate('variantFoil', variant.foil, lang) : undefined - } - }) : variantsToVariantsDetailed(card.variants,lang), - - dexId: card.dexId, - hp: card.hp, - types: card.types?.map((t) => translate('types', t, lang)) as Array, - evolveFrom: card.evolveFrom && resolveText(card.evolveFrom, lang), - weight: card.weight, - description: card.description ? resolveText(card.description, lang) as string : undefined, - level: card.level, - stage: translate('stage', card.stage, lang) as any, - suffix: translate('suffix', card.suffix, lang) as any, - item: card.item ? { - name: resolveText(card.item.name, lang), - effect: resolveText(card.item.effect, lang) - } : undefined, - - abilities: card.abilities?.map((el) => ({ - type: translate('abilityType', el.type, lang) as any, - name: resolveText(el.name, lang), - effect: resolveText(el.effect, lang) - })), - - attacks: card.attacks?.map((el) => ({ - cost: el.cost?.map((t) => translate('types', t, lang)) as Array, - name: resolveText(el.name, lang) as string, - effect: el.effect ? resolveText(el.effect, lang) : undefined, - damage: el.damage - })), - weaknesses: card.weaknesses?.map((el) => ({ - type: translate('types', el.type, lang) as Types, - value: el.value - })), - - resistances: card.resistances?.map((el) => ({ - type: translate('types', el.type, lang) as Types, - value: el.value - })), - - retreat: card.retreat, - - effect: card.effect ? resolveText(card.effect, lang) : undefined, - - trainerType: translate('trainerType', card.trainerType, lang) as any, - energyType: translate('energyType', card.energyType, lang) as any, - regulationMark: card.regulationMark, - - legal: { - standard: cardIsLegal('standard', card, localId), - expanded: cardIsLegal('expanded', card, localId) - }, - boosters: card.boosters ? objectMap(objectPick(card.set.boosters, ...card.boosters), (booster, id) => ({ - id: `boo_${card.set.id}-${id}`, - name: resolveText(booster.name, lang), - // images will be coming soon... - })) : undefined, - updated: await getCardLastEdit(localId, card, lang), - - thirdParty: card.thirdParty - } -} - -/** - * - * @param setName the setname of the card - * @param id the local id of the card - * @returns [the local id, the Card object] - */ -export async function getCard(set: Set, id: string, lang: SupportedLanguages): Promise { - try { - return (await import(`../../${DB_PATH}/${getDataFolder(lang)}/${set.serie.name.en ?? set.serie.name[lang]}/${set.name.en ?? set.name[lang]}/${id}.ts`)).default - } catch { - return (await import(`../../${DB_PATH}/${getDataFolder(lang)}/${set.serie.id}/${set.id}/${id}.ts`)).default - } -} - -/** - * Get cards filtered by the language they are available in - * @param lang the language of the cards - * @param set the set to filter in (optional) - * @returns An array with the 0 = localId, 1 = Card Object - */ -export async function getCards(lang: SupportedLanguages, set?: Set): Promise> { - let cards = await smartGlob(`${DB_PATH}/${getDataFolder(lang)}/${(set && (set.serie.name.en ?? set.serie.name[lang])) ?? '*'}/${(set && (set.name.en ?? set.name[lang])) ?? '*'}/*.ts`) - if (cards.length === 0) { - cards = await smartGlob(`${DB_PATH}/${getDataFolder(lang)}/${(set && set.serie.id) ?? '*'}/${(set && set.id) ?? '*'}/*.ts`) - } - const list: Array<[string, Card]> = [] - for (const path of cards) { - let items = path.split(pathLib.sep) - items = items.slice(items.length - 3) - - // get the card id - let id = items[2] - id = id.substring(0, id.lastIndexOf('.')) - - // get it's set name - const setName = items[1] - - // get it's serie name - const serieName = items[0] - - const set = await getSet(setName, serieName, lang) - - if (!(lang in set.name)) { - continue - } - - // console.log(path, id, set, lang) - const c = await getCard(set, id, lang) - if (!c.name[lang]) { - continue - } - list.push([id, c]) - } - - // Sort by id when possible - return list.sort(([a], [b]) => { - const ra = parseInt(a, 10) - const rb = parseInt(b, 10) - if (!isNaN(ra) && !isNaN(rb)) { - return ra - rb - } - return a >= b ? 1 : -1 - }) -} - -export async function getCardLastEdit(localId: string, card: Card, lang: SupportedLanguages): Promise { - try { - const path = `../${getDataFolder(lang)}/${card.set.serie.name.en}/${card.set.name.en ?? card.set.name.fr}/${localId}.ts` - return getLastEdit(path) - } catch (e) { - try { - const path = `../${getDataFolder(lang)}/${card.set.serie.id}/${card.set.id}/${localId}.ts` - return getLastEdit(path) - } catch (e2) { - console.error(card) - console.error(e) - throw e2 - } - } -} diff --git a/server/compiler/utils/serieUtil.ts b/server/compiler/utils/serieUtil.ts deleted file mode 100644 index 42c0c67665..0000000000 --- a/server/compiler/utils/serieUtil.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Serie, Set, SupportedLanguages } from '../../../interfaces' -import { SerieResume, Serie as SerieSingle } from '../../../meta/definitions/api' -import { getSets, setToSetSimple } from './setUtil' -import { DB_PATH, getDataFolder, resolveText, smartGlob } from './util' - -export async function getSerie(name: string, lang: SupportedLanguages): Promise { - return (await import(`../../${DB_PATH}/${getDataFolder(lang)}/${name}.ts`)).default -} - -//TODO: FIXTHIS this is a workaround for the data-asia as it uses id as the folder name where as the international uses the name -function getSerieIdenti(serie: Serie, lang: SupportedLanguages): string { - return getDataFolder(lang) === 'data' ? serie.name.en : serie.id; -} - -export async function isSerieAvailable(serie: Serie, lang: SupportedLanguages): Promise { - if (!resolveText(serie.name, lang)) { - return false - } - - const sets = await getSets(getSerieIdenti(serie,lang), lang) - return sets.length > 0 -} - -export async function getSeries(lang: SupportedLanguages): Promise> { - let series: Array = (await Promise.all((await smartGlob(`${DB_PATH}/${getDataFolder(lang)}/*.ts`)) - // Find Serie's name - .map((it) => it.substring(it.lastIndexOf('/') + 1, it.length - 3)) - // Fetch the Serie - .map((it) => getSerie(it, lang)))) - // Filter the serie if no name's exists in the selected lang - .filter((serie) => Boolean(resolveText(serie.name, lang))) - - // Filter available series - const isAvailable = await Promise.all(series.map((serie) => isSerieAvailable(serie, lang))) - series = series.filter((_, index) => isAvailable[index]) - - // Sort series by the first set release date - const tmp: Array<[Serie, Set | undefined]> = await Promise.all(series.map(async (it) => [ - it, - ( - await getSets(getSerieIdenti(it,lang), lang)) - .reduce((p, c) => p ? p.releaseDate < c.releaseDate ? p : c : c, undefined) as Set - ] as [Serie, Set])) - - return tmp.sort((a, b) => (a[1] ? a[1].releaseDate : '0') > (b[1] ? b[1].releaseDate : '0') ? 1 : -1).map((it) => it[0]) -} - -export async function serieToSerieSimple(serie: Serie, lang: SupportedLanguages): Promise { - const setsTmp = await getSets(getSerieIdenti(serie,lang), lang) - const sets = await Promise.all(setsTmp - .sort((a, b) => a.releaseDate > b.releaseDate ? 1 : -1) - .map((el) => setToSetSimple(el, lang))) - const logo = sets.find((set) => set.logo)?.logo - return { - id: serie.id, - logo, - name: serie.name[lang] as string - } -} - -export async function serieToSerieSingle(serie: Serie, lang: SupportedLanguages): Promise { - const setsTmp = await getSets(getSerieIdenti(serie,lang), lang) - const sortedSetsTmp = setsTmp.sort((a, b) => a.releaseDate > b.releaseDate ? 1 : -1) - const sets = await Promise.all(sortedSetsTmp.map((el) => setToSetSimple(el, lang))) - const logo = ( - // find the set named after the serie - sets.find((set) => set.name === serie.name[lang]) ?? - // find the first non promo set - sets.find((set) => !set.name.toLowerCase().includes('promo') && set.logo) ?? - // get the first set that contains a logo - sets.find((set) => set.logo) - )?.logo - const releaseDate = sortedSetsTmp[0].releaseDate - - // Final data - return { - id: serie.id, - logo, - name: resolveText(serie.name, lang) as string, - firstSet: sets[0], - lastSet: sets[sets.length - 1], - releaseDate: typeof releaseDate === 'object' ? releaseDate[lang] : releaseDate, - sets - } -} diff --git a/server/compiler/utils/setUtil.ts b/server/compiler/utils/setUtil.ts deleted file mode 100644 index 81856c8dc8..0000000000 --- a/server/compiler/utils/setUtil.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { objectKeys, objectMap } from '@dzeio/object-util' -import { Card, Set, SupportedLanguages } from '../../../interfaces' -import { SetResume, Set as SetSingle } from '../../../meta/definitions/api' -import { cardToCardSimple, getCards } from './cardUtil' -import { DB_PATH, fetchRemoteFile, getDataFolder, resolveText, setIsLegal, smartGlob } from './util' -import path from 'node:path' - - -interface t { - [key: string]: Set -} - -const setCache: t = {} - -export function isSetAvailable(set: Set, lang: SupportedLanguages): boolean { - return !!resolveText(set.name, lang) && !!resolveText(set.serie.name, lang) -} - -/** - * Return the set - * @param name the name of the set - */ -export async function getSet(name: string, serie = '*', lang: SupportedLanguages): Promise { - if (!setCache[name]) { - const file = `${DB_PATH}/${getDataFolder(lang)}/${serie}/${name}.ts` - try { - const [path] = await smartGlob(file) - // console.log(`${DB_PATH}/${getDataFolder(lang)}/${serie}/${name}.ts`) - setCache[name] = (await import(`../../${path}`)).default - } catch (error) { - console.error(error) - console.error(`Error trying to import importing (${file})`) - process.exit(1) - } - } - return setCache[name] -} - -// Dont use cache as it wont necessary have them all -export async function getSets(serie = '*', lang: SupportedLanguages): Promise> { - // list sets names - const rawSets = (await smartGlob(`${DB_PATH}/${getDataFolder(lang)}/${serie}/*.ts`)) - .map((it) => it.replaceAll(path.sep, '/')) - .map((set) => set.substring(set.lastIndexOf('/') + 1, set.lastIndexOf('.'))) - // Fetch sets - const sets = (await Promise.all(rawSets.map((set) => getSet(set, serie, lang)))) - // Filter sets - .filter((set) => isSetAvailable(set, lang)) - // Sort sets by release date - .sort((a, b) => a.releaseDate > b.releaseDate ? 1 : -1) - return sets -} - -export async function getSetPictures(set: Set, lang: SupportedLanguages): Promise<[string | undefined, string | undefined]> { - try { - const file = await fetchRemoteFile('https://assets.tcgdex.net/datas.json') - const logoExists = file[lang]?.[set.serie.id]?.[set.id]?.logo ? `https://assets.tcgdex.net/${lang}/${set.serie.id}/${set.id}/logo` : undefined - const symbolExists = file.univ?.[set.serie.id]?.[set.id]?.symbol ? `https://assets.tcgdex.net/univ/${set.serie.id}/${set.id}/symbol` : undefined - return [ - logoExists, - symbolExists - ] - } catch { - return [undefined, undefined] - } -} - -export async function setToSetSimple(set: Set, lang: SupportedLanguages): Promise { - const cards = await getCards(lang, set) - const pics = await getSetPictures(set, lang) - return { - cardCount: { - official: set.cardCount.official, - total: Math.max(set.cardCount.official, cards.length) - }, - id: set.id, - logo: pics[0], - name: resolveText(set.name, lang), - symbol: pics[1] - } -} - -function getVariantCountForType(card: Card, type: 'normal' | 'reverse' | 'holo' | 'firstEdition'): number { - if( card.variants === undefined || card.variants === null) { - return 0; - } - - if (!Array.isArray(card.variants)) { - return card.variants[type] ? 1 : 0; - } - - if (type === 'firstEdition') { - return card.variants.reduce((count, variant) => - count + (variant.stamp?.some((stamp) => stamp === '1st-edition') ? 1 : 0), 0); - } - - return card.variants.reduce((count, variant) => count + (variant.type === type ? 1 : 0), 0); -} - - -export async function setToSetSingle(set: Set, lang: SupportedLanguages): Promise { - const cards = await getCards(lang, set) - const pics = await getSetPictures(set, lang) - return { - cardCount: { - firstEd: cards.reduce((count, card) => count + getVariantCountForType(card[1],"firstEdition"), 0), - holo: cards.reduce((count, card) => count + getVariantCountForType(card[1],"holo"), 0), - normal: cards.reduce((count, card) => count + getVariantCountForType(card[1],"normal"), 0), - official: set.cardCount.official, - reverse: cards.reduce((count, card) => count + getVariantCountForType(card[1],"reverse"), 0), - total: Math.max(set.cardCount.official, cards.length) - }, - cards: await Promise.all(cards.map(([id, card]) => cardToCardSimple(id, card, lang))), - id: set.id, - legal: { - expanded: setIsLegal('expanded', set), - standard: setIsLegal('standard', set) - }, - logo: pics[0], - name: resolveText(set.name, lang), - releaseDate: typeof set.releaseDate === 'object' ? set.releaseDate[lang] ?? set.releaseDate[objectKeys(set.releaseDate)[0]]! : set.releaseDate, - serie: { - id: set.serie.id, - name: resolveText(set.serie.name, lang) - }, - symbol: pics[1], - tcgOnline: set.tcgOnline, - abbreviation: (set.abbreviations?.official || resolveText(set.abbreviations, lang)) ? { - official: set.abbreviations?.official, - localized: resolveText(set.abbreviations, lang) - } : undefined, - boosters: set.boosters ? objectMap(set.boosters, (booster, id) => ({ - id: `boo_${set.id}-${id}`, - name: resolveText(booster.name, lang), - // images will be coming soon... - })) : undefined, - thirdParty: set.thirdParty - } -} diff --git a/server/compiler/utils/translationUtil.ts b/server/compiler/utils/translationUtil.ts deleted file mode 100644 index cddef4304e..0000000000 --- a/server/compiler/utils/translationUtil.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { SupportedLanguages } from '../../../interfaces' -import es from '../../../meta/translations/es.json' -import it from '../../../meta/translations/it.json' -import pt from '../../../meta/translations/pt.json' -import de from '../../../meta/translations/de.json' -import fr from '../../../meta/translations/fr.json' - -type translatable = 'types' | 'rarity' | 'stage' | 'category' | 'suffix' | 'abilityType' | 'trainerType' | 'energyType' | 'variantType' | 'variantSize' | 'variantFoil' | 'variantStamp' | 'variantSubtype' - -const translations: Record>> = { - es, - fr, - it, - pt, - de -} - -export default function translate(item: translatable, key: string | undefined, lang: SupportedLanguages): string | undefined { - if (!key) { - return key - } - // Temporary trenslations are in english while they are being worked on - if (lang === 'en' || !Object.keys(translations).includes(lang)) { - return key - } - const res = translations[lang]?.[item]?.[key] - if (!res) { - throw new Error(`Could not find translation for ${lang}.${item}.${key}`) - } - return res -} diff --git a/server/compiler/utils/util.ts b/server/compiler/utils/util.ts deleted file mode 100644 index a3279e95b7..0000000000 --- a/server/compiler/utils/util.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { objectSize } from '@dzeio/object-util' -import Queue from '@dzeio/queue' -import { glob } from 'glob' -import { exec, spawn } from 'node:child_process' -import { writeFileSync } from 'node:fs' -import { Card, Languages, Set, SupportedLanguages } from '../../../interfaces' -import * as legals from '../../../meta/legals' -interface fileCacheInterface { - [key: string]: any -} - -export const DB_PATH = "../" - -const fileCache: fileCacheInterface = {} - -/** - * Fetch a JSON file from a remote location - * @param url the URL to fetch - * @returns the JSON file content - */ -export async function fetchRemoteFile(url: string): Promise { - if (!fileCache[url]) { - const signal = new AbortController() - - const finished = setTimeout(() => { - signal.abort() - }, 60 * 1000); - - const resp = await fetch(url, { - signal: signal.signal - }) - clearTimeout(finished) - fileCache[url] = resp.json() - } - return fileCache[url] -} - -const globCache: Record> = {} - -export async function smartGlob(query: string): Promise> { - if (!globCache[query]) { - globCache[query] = await glob(query) - } - return globCache[query] -} - -/** - * Check if a card is currently Legal - * @param type the type of legality - * @param card the card to check - * @param localId the card localid - * @returns {boolean} if the card is currently in the legal type - */ -export function cardIsLegal(type: 'standard' | 'expanded', card: Card, localId: string): boolean { - const legal = legals[type] - if ( - legal.includes.series.includes(card.set.serie.id) || - legal.includes.sets.includes(card.set.id) || - card.energyType === "Normal" || - card.regulationMark && legal.includes.regulationMark.includes(card.regulationMark) - ) { - return !( - legal.excludes.sets.includes(card.set.id) || - (type === 'standard' && card.types?.includes("Fairy")) || - legal.excludes.cards.includes(`${card.set.id}-${localId}`) - ) - } - - return false; -} - -/** - * Check if a set is currently Legal - * @param type the type of legality - * @param set the set to check - * @returns {boolean} if the set is currently in the legal type - */ -export function setIsLegal(type: 'standard' | 'expanded', set: Set): boolean { - const legal = legals[type] - if ( - legal.includes.series.includes(set.serie.id) || - legal.includes.sets.includes(set.id) - ) { - return !legal.excludes.sets.includes(set.id) - } - return false -} - -export function getDataFolder(lang: SupportedLanguages) { - return ['ja', 'ko', 'zh-tw', 'id', 'th', 'zh-cn'].includes(lang) ? 'data-asia' : 'data' -} - -/** - * run a command on the OS, it uses Spawn by default because exec seems to have a bug linked to the Buffer - * - * @param command the command to run - * @param useSpawn select the method to use to run the command - * @returns a string with the stdout - */ -function runCommand(command: string, useSpawn = true): Promise { - if (!useSpawn) { - return new Promise((res, rej) => { - exec(command, (err, out) => { - if (err) { - rej(err) - } - res(out) - }) - }) - } - const splitted = command.split(' ') - command = splitted.shift()! - - return new Promise((res, rej) => { - const cmd = spawn(command, splitted) - let out: string = '' - cmd.stdout.on('data', (data) => { - out += data.toString() - }) - - cmd.on('close', (code) => { - if (code !== 0) { - console.log(`command exited with code ${code}`); - rej(code) - return - } - res(out) - }) - }) -} - -const lastEditsCache: Record = {} -export async function loadLastEdits() { - console.log('Loading Git File Tree...') - const firstCommand = 'git ls-tree -r --name-only HEAD ../data' - const files = (await runCommand(firstCommand)).split('\n') - const secondCommand = 'git ls-tree -r --name-only HEAD ../data-asia' - files.push(...(await runCommand(secondCommand)).split('\n')) - console.log('Loaded files tree', files.length, 'files') - console.log('Loading their last edit time') - let processed = 0 - const concurrent = process.platform === 'win32' ? 10 : 1000 - const queue = new Queue(concurrent, 10) - queue.start() - - for await (let file of files) { - file = file.replace(/"/g, '').replace("\\303\\251", "é") - await queue.add(runCommand(`git log -1 --pretty="format:%cd" --date=iso-strict "${file}"`, false).then((res) => { - lastEditsCache[file] = res - }) - .catch(() => { - console.warn('could not load file', file, 'hope it does not break everything else lol') - }) - .finally(() => { - processed++ - if (processed % 1000 === 0) { - console.log('loaded', processed, 'out of', files.length, 'files', `(${(processed / files.length * 100).toFixed(0)}%)`) - } - })) - // try { - // // don't really know why but it does not correctly execute the command when using Spawn - // lastEditsCache[file] = await runCommand(`git log -1 --pretty="format:%cd" --date=iso-strict "${file}"`, false) - // } catch { - // console.warn('could not load file', file, 'hope it does not break everything else lol') - // } - // processed++ - // if (processed % 1000 === 0) { - // console.log('loaded', processed, 'out of', files.length, 'files', `(${(processed / files.length * 100).toFixed(0)}%)`) - // } - } - await queue.waitEnd() - console.log('done loading files', objectSize(lastEditsCache)) -} - -export function getLastEdit(path: string): string { - const date = lastEditsCache[path] - if (!date) { - return new Date().toISOString() - // throw new Error(`edit date not found for file ${path}`) - } - return date -} - -export function resolveText(text: Languages | undefined, lang: SupportedLanguages): T | undefined { - if (!text) return text as undefined - let res: T | undefined = text[lang] - if (typeof res === 'undefined' && !lang.includes('-')) { - const key = Object.keys(text).find(key => key.startsWith(lang)) - return text[key as keyof Languages] - } - return res -} diff --git a/server/out.gql b/server/out.gql new file mode 100644 index 0000000000..a270582ded --- /dev/null +++ b/server/out.gql @@ -0,0 +1,314 @@ +type Card { + """Unique identifier of the card""" + id: String! + + """Card number within its set""" + localId: String! + + """URL to the card image""" + image: String + + """Name of the card (including any suffix)""" + name: String! + + """Pricing information for the card""" + pricing: PricingSub1 + + """Artist who illustrated the card""" + illustrator: String + + """Card category (Pokemon, Trainer, or Energy)""" + category: String! + + """Card rarity (Common, Uncommon, Rare, etc.)""" + rarity: String! + + """The set this card belongs to""" + set: SetSub1! + + """Indicates which variants of this card exist (overrides set variants)""" + variants: VariantsSub1 + + """Detailed information about a card variant""" + variant_detailed: [Variant_detailedSub1] + + """Hit Points (HP) of the Pokemon""" + hp: Float + + """Energy types of the Pokemon""" + types: [String] + + """Name of the Pokemon this evolves from""" + evolveFrom: String + + """Evolution stage (Basic, Stage 1, Stage 2, etc.)""" + stage: String + + """Special card suffix (EX, GX, V, etc.)""" + suffix: String + + """Pokemon's held item information""" + item: ItemSub1 + + """Type of trainer card (Item, Supporter, Stadium, Tool)""" + trainerType: String + + """Type of energy card (Basic, Special)""" + energyType: String + + """Regulation mark on cards (introduced in Sword & Shield)""" + regulationMark: String + + """Information about tournament legality of this card""" + legal: LegalSub1! + + """Descriptive text or flavor text on the card""" + description: String + + """Level of the Pokemon (can be a string 'X' for LEVEL-UP cards)""" + level: String + + """Pokemon abilities (Poké-Power, Poké-Body, Ability, etc.)""" + abilities: [AbilitiesSub1] + + """Pokemon attacks""" + attacks: [AttacksSub1] + + """Retreat cost of the Pokemon""" + retreat: Float + + """Pokedex number(s) of the Pokemon""" + dexId: [Float] + + """Types the Pokemon is weak against""" + weaknesses: [WeaknessesSub1] + + """Types the Pokemon is resistant to""" + resistances: [ResistancesSub1] + + """Boosters in which this card is available""" + boosters: [BoostersSub1] + + """Timestamp of when this card data was last updated""" + updated: String! +} + +type BoostersSub1 { + """The booster ID using format boo_-""" + id: String! + + """The name of the booster""" + name: String! + + """URL to the logo of the booster""" + logo: String + + """URL to the front artwork of the booster pack""" + artwork_front: String + + """URL to the back artwork of the booster pack""" + artwork_back: String +} + +type ResistancesSub1 { + type: String + + value: String +} + +type WeaknessesSub1 { + type: String + + value: String +} + +type AttacksSub1 { + """Energy cost to use this attack""" + cost: [String] + + """Name of the attack""" + name: String! + + """Effect text of the attack""" + effect: String + + """Base damage of the attack (can be string for special damage)""" + damage: String +} + +type AbilitiesSub1 { + """Type of ability (Poké-Power, Poké-Body, Ability, etc.)""" + type: String + + """Name of the ability""" + name: String + + """Effect text of the ability""" + effect: String +} + +type LegalSub1 { + """Indicates whether this card is legal in standard format tournaments""" + standard: Boolean + + """Indicates whether this card is legal in expanded format tournaments""" + expanded: Boolean +} + +type ItemSub1 { + """Name of the held item""" + name: String! + + """Effect of the held item""" + effect: String! +} + +type Variant_detailedSub1 { + """The type of variant (e.g., normal, reverse, holo, etc.)""" + type: String! + + """The size of the variant (e.g., standard, jumbo, etc.)""" + size: String + + """The stamps of the variant (e.g., 'Staff', 'Pokemon-Centre', etc.)""" + stamp: [String] + + """The foil of the variant (e.g., 'Pokeball', 'MasterBall', etc.)""" + foil: String +} + +type VariantsSub1 { + """Indicates whether a normal variant exists""" + normal: Boolean! + + """Indicates whether a reverse holo variant exists""" + reverse: Boolean! + + """Indicates whether a holo variant exists""" + holo: Boolean! + + """Indicates whether a first edition variant exists""" + firstEdition: Boolean! + + """Indicates whether a promotional variant exists""" + wPromo: Boolean! +} + +type SetSub1 { + """Unique identifier of the set""" + id: String! + + """Name of the set""" + name: String! + + """URL to the logo of the set""" + logo: String + + """URL to the set symbol""" + symbol: String + + """Statistics about the number of cards in this set""" + cardCount: CardCountSub3! +} + +type CardCountSub3 { + """Total number of cards in the set including variants""" + total: Float! + + """Number of cards officially numbered in the set""" + official: Float! +} + +type PricingSub1 { + tcgplayer: TcgplayerSub3 + + """Cardmarket pricing information""" + cardmarket: CardmarketSub3 +} + +type CardmarketSub3 { + """Date and time when the pricing information was last updated""" + updated: String + + """Currency unit of the price""" + unit: String + + """Average price of the card""" + avg: Float + + """Lowest price of the card""" + low: Float + + """Trend of the card price""" + trend: Float + + """Average price of the card in the last 1 day""" + avg1: Float + + """Average price of the card in the last 7 days""" + avg7: Float + + """Average price of the card in the last 30 days""" + avg30: Float + + """Average price of the card in the last 30 days, including holographic cards""" + avg-holo: Float + + """Trend of the card's price in the last 30 days, including holographic cards""" + trend-holo: Float + + """Average price of the card in the last 1 day, including holographic cards""" + avg1-holo: Float + + """Average price of the card in the last 7 days, including holographic cards""" + avg7-holo: Float + + """Average price of the card in the last 30 days, including holographic cards""" + avg30-holo: Float +} + +type TcgplayerSub3 { + """Date and time when the pricing information was last updated""" + updated: String + + """Currency unit of the price""" + unit: String + + normal: NormalSub5 + + reverse: ReverseSub5 +} + +type ReverseSub5 { + """Lowest price for the card""" + lowPrice: Float + + """Middle price for the card""" + midPrice: Float + + """Highest price for the card""" + highPrice: Float + + """Market price for the card""" + marketPrice: Float + + """Direct low price for the card""" + directLowPrice: Float +} + +type NormalSub5 { + """Lowest price for the card""" + lowPrice: Float + + """Middle price for the card""" + midPrice: Float + + """Highest price for the card""" + highPrice: Float + + """Market price for the card""" + marketPrice: Float + + """Direct low price for the card""" + directLowPrice: Float +} diff --git a/server/package.json b/server/package.json index e9776bf169..b25224d596 100644 --- a/server/package.json +++ b/server/package.json @@ -31,6 +31,7 @@ "@types/swagger-ui-express": "^4.1.8", "@typescript/native-preview": "^7.0.0-dev.20250805.1", "glob": "^10.4.5", + "openapi-typescript": "^7.10.1", "typescript": "^4.9.5" } } diff --git a/server/src/V2/Components/Card.ts b/server/src/V2/Components/Card.ts index bc68240690..892fb6beb9 100644 --- a/server/src/V2/Components/Card.ts +++ b/server/src/V2/Components/Card.ts @@ -1,97 +1,35 @@ import Cache from '@cachex/memory' -import { objectOmit } from '@dzeio/object-util' -import type { CardResume, Card as SDKCard } from '@tcgdex/sdk' +import { objectClean, objectKeys } from '@dzeio/object-util' import { SupportedLanguages } from '@tcgdex/sdk' -import de from '../../../generated/de/cards.json' -import en from '../../../generated/en/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' -import ja from '../../../generated/ja/cards.json' -import ko from '../../../generated/ko/cards.json' -import nl from '../../../generated/nl/cards.json' -import pl from '../../../generated/pl/cards.json' -import ptbr from '../../../generated/pt-br/cards.json' -import ptpt from '../../../generated/pt-pt/cards.json' -import pt from '../../../generated/pt/cards.json' -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 type { CompiledCard } from '../../../../scripts/compiler/interfaces' +import dataTMP from '../../../generated/cards.json' +import type { Card, CardResume } from '../../api' +import { Version } from '../../interfaces' import { getCardMarketPrice } from '../../libs/providers/cardmarket' import { getTCGPlayerPrice } from '../../libs/providers/tcgplayer' import { executeQuery, type Query } from '../../libs/QueryEngine/filter' +import { loadSet } from './Set' -// 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.toLowerCase()}en`] = it) -// @ts-ignore ts can't load file -fr.forEach((it) => list[`${it.id.toLowerCase()}fr`] = it) -// @ts-ignore ts can't load file -es.forEach((it) => list[`${it.id.toLowerCase()}es`] = it) -esmx.forEach((it) => list[`${it.id.toLowerCase()}es-mx`] = it) -// @ts-ignore ts can't load file -it.forEach((it) => list[`${it.id.toLowerCase()}it`] = it) -// @ts-ignore ts can't load file -pt.forEach((it) => list[`${it.id.toLowerCase()}pt`] = it) -ptbr.forEach((it) => list[`${it.id.toLowerCase()}pt-br`] = it) -// @ts-expect-error there is currently not cards here -ptpt.forEach((it) => list[`${it.id.toLowerCase()}pt-pt`] = it) -// @ts-ignore ts can't load file -de.forEach((it) => list[`${it.id.toLowerCase()}de`] = it) -// @ts-expect-error there is currently not cards here -nl.forEach((it) => list[`${it.id.toLowerCase()}nl`] = it) -// @ts-expect-error there is currently not cards here -pl.forEach((it) => list[`${it.id.toLowerCase()}pl`] = it) -// @ts-expect-error there is currently not cards here -ru.forEach((it) => list[`${it.id.toLowerCase()}ru`] = it) -ja.forEach((it) => list[`${it.id.toLowerCase()}ja`] = it) -// @ts-expect-error there is currently not cards here -ko.forEach((it) => list[`${it.id.toLowerCase()}ko`] = it) -// @ts-ignore ts can't load file -zhtw.forEach((it) => list[`${it.id.toLowerCase()}zh-tw`] = it) -id.forEach((it) => list[`${it.id.toLowerCase()}id`] = it) -th.forEach((it) => list[`${it.id.toLowerCase()}th`] = it) -zhcn.forEach((it) => list[`${it.id.toLowerCase()}zh-cn`] = it) - -const cards = { - en: en, - fr: fr, - es: es, - 'es-mx': esmx, - it: it, - pt: pt, - 'pt-br': ptbr, - 'pt-pt': ptpt, - de: de, - nl: nl, - pl: pl, - ru: ru, - ja: ja, - ko: ko, - 'zh-tw': zhtw, - id: id, - th: th, - 'zh-cn': zhcn, -} as const +const data = dataTMP as Array -const cache = new Cache() - -type MappedCard = SDKCard // (typeof en)[number] +// Compile data subsets +const list: Record = {} +const langLists: Record> = {} -export type Card = SDKCard +data.forEach((it) => { + list[it.id.toLowerCase()] = it + objectKeys(it.name).forEach((lang) => { + langLists[lang] ??= [] + langLists[lang].push(it) + }) +}) -export async function getAllCards(lang: SupportedLanguages): Promise> { - return Promise.all((cards[lang] as Array).map((it) => loadCard(lang, it.id))) as Promise> -} +// setup cache +const cache = new Cache() -export function getCompiledCard(lang: SupportedLanguages, id: string): any { - const key = `${id}${lang}`.toLowerCase() as `${any}${string}` - return list[key] +export async function getAllCards(lang: SupportedLanguages, version: Version = 'full'): Promise> { + return (await Promise.all((langLists[lang]).map((it) => loadCard(lang, it.id, version as 'full')))) + .filter((it) => !!it) } /** @@ -101,9 +39,26 @@ export function getCompiledCard(lang: SupportedLanguages, id: string): any { * @param lang * @param id */ -async function loadCard(lang: SupportedLanguages, id: string): Promise { +export async function loadCard(lang: SupportedLanguages, id: string, version?: 'full'): Promise +export async function loadCard(lang: SupportedLanguages, id: string, version: 'brief'): Promise +export async function loadCard(lang: SupportedLanguages, id: string, version: 'full' | 'brief' = 'full'): Promise { + // fucking important to allow users to fetch using IDs in any casing + id = id.toLowerCase() + if (version === 'brief') { + const card = list[id] + if (!card) { + return undefined + } + return { + // resume + id: card.id, + localId: card.localId, + name: card.name[lang]!, + image: card.image?.[lang], + } + } const key = `${id}${lang}`.toLowerCase() - const value = cache.get(key) + const value = cache.get(key) // expect the cache to be present if (value) { @@ -112,10 +67,9 @@ async function loadCard(lang: SupportedLanguages, id: string): Promise it[lang]!), + evolveFrom: card.evolveFrom?.[lang], + // weight: card.weight, + description: card.description?.[lang], + level: card.level, + stage: card.stage?.[lang], + suffix: card.suffix?.[lang], + item: card.item ? { + name: card.item.name[lang]!, + effect: card.item.effect[lang]! + } : undefined, + abilities: card.abilities?.map((it) => ({ + type: it.type[lang]!, + name: it.name[lang]!, + effect: it.effect[lang]! + })), + attacks: card.attacks?.map((it) => ({ + cost: it.cost?.map((cost) => cost[lang]!), + name: it.name[lang]!, + effect: it.effect?.[lang], + damage: it.damage + })), + weaknesses: card.weaknesses?.map((it) => ({ + type: it.type[lang]!, + value: it.value + })), + resistances: card.resistances?.map((it) => ({ + type: it.type[lang]!, + value: it.value + })), + retreat: card.retreat, + effect: card.effect?.[lang], + trainerType: card.trainerType?.[lang], + energyType: card.energyType?.[lang], + regulationMark: card.regulationMark, + legal: card.legal, + + updated: card.updated, + + set: (await loadSet(card.set, lang, 'brief'))!, + pricing: { cardmarket: cardmarket, tcgplayer: tcgplayer } - } as SDKCard + } satisfies Card // console.timeEnd('remapping card') + // console.time('cleaning card') + objectClean(res) + // console.timeEnd('cleaning card') + cache.set(key, res, 60 * 60) // console.timeEnd(`loading card ${id}${lang}`) return res @@ -144,11 +157,11 @@ export async function getCardById(lang: SupportedLanguages, id: string) { return loadCard(lang, id) } -export async function findCards(lang: SupportedLanguages, query: Query) { +export async function findCards(lang: SupportedLanguages, query: Query) { return executeQuery(await getAllCards(lang), query).data } -export async function findOneCard(lang: SupportedLanguages, query: Query) { +export async function findOneCard(lang: SupportedLanguages, query: Query) { const res = await findCards(lang, query) if (res.length === 0) { return undefined @@ -156,9 +169,14 @@ export async function findOneCard(lang: SupportedLanguages, query: Query = {} +const langLists: Record> = {} -type MappedSerie = any // (typeof en)[number] +const data = dataTMP as Array -export async function getAllSeries(lang: SupportedLanguages): Promise> { - return Promise.all((series[lang] as Array).map(transformSerie)) -} +data.forEach((it) => { + list[it.id.toLowerCase()] = it + objectKeys(it.name).forEach((lang) => { + langLists[lang] ??= [] + langLists[lang].push(it) + }) +}) -async function transformSerie(serie: MappedSerie): Promise { - return serie +export async function getAllSeries(lang: SupportedLanguages, version: Version = 'full'): Promise> { + return (await Promise.all((langLists[lang]).map((it) => loadSerie(it.id, lang, version)))) + .filter((it) => !!it) } -export async function findSeries(lang: SupportedLanguages, query: Query) { +export async function findSeries(lang: SupportedLanguages, query: Query) { return executeQuery(await getAllSeries(lang), query).data } + +export async function loadSerie(id: string, lang: SupportedLanguages, version?: Version): Promise +export async function loadSerie(id: string, lang: SupportedLanguages, version: 'brief'): Promise +export async function loadSerie(id: string, lang: SupportedLanguages, version: Version = 'full'): Promise { + const serie = langLists[lang].find((it) => it.id.toLowerCase() === id.toLowerCase()) + if (!serie) { + return undefined + } + const brief = { + id: serie.id, + name: serie.name[lang]! + } satisfies SerieResume + if (version === 'brief') { + return brief + } + return { + ...serie, + ...brief, + // @ts-expect-error + firstSet: await loadSet(serie.firstSet, lang, 'brief'), + // @ts-expect-error + lastSet: await loadSet(serie.lastSet, lang, 'brief'), + // @ts-expect-error + sets: await Promise.all(serie.sets.map((it) => loadSet(it, lang, 'brief'))) + } satisfies Serie +} + export async function findOneSerie(lang: SupportedLanguages, query: Query) { const res = await findSeries(lang, query) if (res.length === 0) { @@ -63,7 +66,7 @@ export async function findOneSerie(lang: SupportedLanguages, query: Query -type MappedSet = any // (typeof en)[number] +// Compile data subsets +const list: Record = {} +export const langLists: Record> = {} -export async function getAllSets(lang: SupportedLanguages): Promise> { - return Promise.all((sets[lang] as Array).map(transformSet)) -} +data.forEach((it) => { + list[it.id.toLowerCase()] = it + objectKeys(it.name).forEach((lang) => { + langLists[lang] ??= [] + langLists[lang].push(it) + }) +}) -async function transformSet(set: MappedSet): Promise { - return { - ...objectOmit(set, 'thirdParty'), - // pricing: { - // cardmarket: await getCardMarketPrice(card), - // tcgplayer: await getTCGPlayerPrice(card) - // } - } +export async function getAllSets(lang: SupportedLanguages, version: Version = 'full'): Promise> { + return Promise.all((langLists[lang] as Array).map((it) => loadSet(it.id, lang, version as 'full') as Promise)) } -export async function findSets(lang: SupportedLanguages, query: Query) { - return executeQuery(await getAllSets(lang), query).data +export async function findSets(lang: SupportedLanguages, query: Query, version: Version = 'full') { + return executeQuery(await getAllSets(lang, version), query).data } export async function findOneSet(lang: SupportedLanguages, query: Query) { @@ -70,6 +39,39 @@ export async function findOneSet(lang: SupportedLanguages, query: Query) return res[0] } +export async function loadSet(id: string, lang: SupportedLanguages, version: 'full'): Promise +export async function loadSet(id: string, lang: SupportedLanguages, version: 'brief'): Promise +export async function loadSet(id: string, lang: SupportedLanguages, version: Version = 'full'): Promise { + const set = langLists[lang].find((it) => it.id.toLowerCase() === id.toLowerCase()) + if (!set) { + return undefined + } + const brief = { + id: set.id, + name: set.name[lang]!, + logo: set.logo?.[lang], + symbol: set.symbol, + cardCount: { + total: set.cards.length, + official: set.cardCount.official + } + } satisfies SetResume + if (version === 'brief') { + return brief + } + return { + ...objectOmit(set, 'thirdParty'), + ...brief, + releaseDate: set.releaseDate[lang]!, + cards: await Promise.all(set.cards.map((it) => loadCard(lang, it, 'brief') as Promise)), + serie: (await loadSerie(set.serie, lang, 'brief'))!, + boosters: set.boosters?.map((it) => ({ + id: it.id, + name: it.name[lang]! + })) + } satisfies SDKSet +} + export function setToBrief(set: SDKSet): SetResume { return { id: set.id, diff --git a/server/src/V2/endpoints/jsonEndpoints.ts b/server/src/V2/endpoints/jsonEndpoints.ts index 31f9062608..b768c5f2e6 100644 --- a/server/src/V2/endpoints/jsonEndpoints.ts +++ b/server/src/V2/endpoints/jsonEndpoints.ts @@ -3,13 +3,16 @@ import type { Card as SDKCard } from '@tcgdex/sdk' import apicache from 'apicache' import express, { type Request } from 'express' import { Errors, sendError } from '../../libs/Errors' +import { listSKUs } from '../../libs/providers/tcgplayer' import type { Query } from '../../libs/QueryEngine/filter' import { recordToQuery } from '../../libs/QueryEngine/parsers' +import type { paths as OpenAPI } from '../../openapi' import { betterSorter, checkLanguage, unique } from '../../util' -import { getAllCards, findOneCard, findCards, toBrief, getCardById, getCompiledCard } from '../Components/Card' -import { findOneSet, findSets, setToBrief } from '../Components/Set' +import { findCards, findOneCard, getAllCards, getCardById, toBrief } from '../Components/Card' import { findOneSerie, findSeries, serieToBrief } from '../Components/Serie' -import { listSKUs } from '../../libs/providers/tcgplayer' +import { findOneSet, findSets, setToBrief } from '../Components/Set' + +type Response = OpenAPI[Path]['get']['responses'][200]['content']['application/json'] type CustomRequest = Request & { /** @@ -21,7 +24,7 @@ type CustomRequest = Request & { const server = express.Router() -const endpointToField: Record = { +const endpointToField = { categories: 'category', 'energy-types': 'energyType', hp: 'hp', @@ -38,11 +41,11 @@ const endpointToField: Record = { sets: "set", types: "types", variants: "variants", -} +} satisfies Record server // Midleware that handle caching only in production and on GET requests - .use(apicache.middleware('1 day', (req: CustomRequest, res: Response) => !req.DO_NOT_CACHE && res.status < 400 && process.env.NODE_ENV === 'production' && req.method === 'GET', {})) + .use(apicache.middleware('1 hour', (req: CustomRequest, res: any) => !req.DO_NOT_CACHE && res.status < 400 && process.env.NODE_ENV === 'production' && req.method === 'GET', {})) // .get('/cache/performance', (req, res) => { // res.json(apicache.getPerformance()) @@ -90,13 +93,13 @@ server let data: Array = [] switch (what.toLowerCase()) { case 'card': - data = await findCards(lang, query) + data = await findCards(lang, query) satisfies Array> break case 'set': - data = await findSets(lang, query) + data = await findSets(lang, query) satisfies Array> break case 'serie': - data = await findSeries(lang, query) + data = await findSeries(lang, query) satisfies Array> break default: sendError(Errors.NOT_FOUND, res, { details: `You can only run random requests on "card", "set" or "serie" while you did on "${what}"` }) @@ -140,7 +143,8 @@ server }] } result = (await findCards(lang, query)) - .map(toBrief) + .map(toBrief) satisfies Response<'/cards'> + break } @@ -154,12 +158,12 @@ server 'serie.name': tmp }] } - result = (await findSets(lang, query)).map(setToBrief) + result = (await findSets(lang, query)).map(setToBrief) satisfies Response<'/sets'> break } case 'series': result = (await findSeries(lang, query)) - .map(serieToBrief) + .map(serieToBrief) satisfies Response<'/series'> break case 'categories': case "energy-types": @@ -175,7 +179,17 @@ server (await getAllCards(lang)) .map((c) => c[endpointToField[endpoint]] as string) .filter((c) => c) - ).sort(betterSorter) + ).sort(betterSorter) satisfies + | Response<'/categories'> + | Response<'/energy-types'> + | Response<'/hp'> + | Response<'/illustrators'> + | Response<'/rarities'> + | Response<'/regulation-marks'> + | Response<'/retreats'> + | Response<'/stages'> + | Response<'/suffixes'> + | Response<'/trainer-types'> break case "types": case "dex-ids": @@ -184,7 +198,9 @@ server .map((c) => c[endpointToField[endpoint]] as Array) .filter((c) => c) .reduce((p, c) => [...p, ...c], [] as Array) - ).sort(betterSorter) + ).sort(betterSorter) satisfies + | Response<'/types'> + | Response<'/dex-ids'> break case "variants": result = unique( @@ -192,7 +208,7 @@ server .map((c) => objectKeys(c.variants ?? {}) as Array) .filter((c) => c) .reduce((p, c) => [...p, ...c], [] as Array) - ).sort() + ).sort() satisfies Response<'/variants'> break default: sendError(Errors.NOT_FOUND, res, { endpoint }) @@ -226,45 +242,57 @@ server let result: unknown switch (endpoint) { case 'cards': - // console.time('card') - result = await getCardById(lang, id) + // console.time('card', id) + result = await getCardById(lang, id) satisfies Response<'/cards/{cardId}'> | undefined if (!result) { - result = await findOneCard(lang, { name: id }) + result = await findOneCard(lang, { name: id }) satisfies Response<'/cards/{cardId}'> | undefined } - // console.timeEnd('card') break case 'sets': - result = await findOneSet(lang, { id }) + result = await findOneSet(lang, { id }) satisfies Response<'/sets/{set}'> | undefined if (!result) { - result = await findOneSet(lang, { name: id }) + result = await findOneSet(lang, { name: id }) satisfies Response<'/sets/{set}'> | undefined } break case 'series': - result = await findOneSerie(lang, { id }) + result = await findOneSerie(lang, { id }) satisfies Response<'/series/{serie}'> | undefined if (!result) { - result = await findOneSerie(lang, { name: id }) + result = await findOneSerie(lang, { name: id }) satisfies Response<'/series/{serie}'> | undefined } break case 'dex-ids': { result = { - name: parseInt(id, 10), + name: Number.parseInt(id, 10), // @ts-expect-error current behavior is normal - cards: (await findCards(lang, { dexId: { $eq: parseInt(id, 10) }})) + cards: (await findCards(lang, { dexId: { $eq: parseInt(id, 10) } })) .map(toBrief) - } + } satisfies Response<'/dex-ids/{dexId}'> | undefined break } default: - if (!endpointToField[endpoint]) { + if (!(endpoint in endpointToField)) { break } result = { name: id, - cards: (await findCards(lang, { [endpointToField[endpoint]]: id })) + cards: (await findCards(lang, { [endpointToField[endpoint as 'hp']]: id } as any)) .map(toBrief) - } + } satisfies + | Response<'/categories/{category}'> + | Response<'/energy-types/{energy-type}'> + | Response<'/hp/{hp}'> + | Response<'/illustrators/{illustrator}'> + | Response<'/rarities/{rarity}'> + | Response<'/regulation-marks/{regulation-mark}'> + | Response<'/retreats/{retreat}'> + | Response<'/stages/{stage}'> + | Response<'/suffixes/{suffix}'> + | Response<'/trainer-types/{trainer-type}'> + | Response<'/types/{type}'> + | Response<'/variants/{variant}'> + | undefined } // console.timeEnd('request') @@ -298,13 +326,13 @@ server switch (endpoint) { case 'cards': if (subid === 'skus') { - result = await listSKUs(getCompiledCard(lang, id)) + result = await listSKUs(await getCardById(lang, id) as any) } break case 'sets': // allow the dev to use a non prefixed value like `10` instead of `010` for newer sets // @ts-expect-error normal behavior until the filtering is more fiable - result = await findOneCard(lang, { localId: { $or: [subid.padStart(3, '0'), subid] }, $or: [{ 'set.id': id }, { 'set.name': id }] }) + result = await findOneCard(lang, { localId: { $or: [subid.padStart(3, '0'), subid] }, $or: [{ 'set.id': id }, { 'set.name': id }] }) satisfies Response<'/sets/{set}/{cardLocalId}'> | undefined break } if (!result) { diff --git a/server/src/V2/graphql/resolver.ts b/server/src/V2/graphql/resolver.ts index ea8c14e3c9..a7fa26d3f4 100644 --- a/server/src/V2/graphql/resolver.ts +++ b/server/src/V2/graphql/resolver.ts @@ -29,6 +29,11 @@ const middleware = (fn: (lang: SupportedLanguages, query: Query) => any) // get the locale directive const lang = getLang(e) + if (typeof data.id !== 'undefined') { + data.filters ??= {} + data.filters['id'] = `eq:${data.id}` + } + const query = recordToQuery(data.filters ?? data) // Deprecated code handling diff --git a/server/src/api.d.ts b/server/src/api.d.ts new file mode 100644 index 0000000000..ee3e9c51be --- /dev/null +++ b/server/src/api.d.ts @@ -0,0 +1,13 @@ +import type { components, paths } from './openapi' + +export type SerieResume = components['schemas']['SerieResume'] +export type Serie = components['schemas']['Serie'] + +export type SetResume = components['schemas']['SetResume'] +export type Set = components['schemas']['Set'] + +export type CardResume = components['schemas']['CardResume'] +export type Card = components['schemas']['Card'] + +export declare type StringEndpointList = Array; +export type StringEndpoint = components['schemas']['StringEndpoint'] diff --git a/server/src/index.ts b/server/src/index.ts index ccc8167abe..ef4406dff7 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,14 +1,14 @@ import express, { type Response } from 'express' -import jsonEndpoints from './V2/endpoints/jsonEndpoints' -import openapi from './V2/endpoints/openapi' -import graphql from './V2/graphql' import cluster from 'node:cluster' import { availableParallelism } from "node:os" import { Errors, sendError } from './libs/Errors' -import status from './status' +import jsonEndpoints from './V2/endpoints/jsonEndpoints' +import openapi from './V2/endpoints/openapi' +import graphql from './V2/graphql' import * as Sentry from "@sentry/node" import { updateDatas } from './libs/providers/cardmarket' import { updateTCGPlayerDatas } from './libs/providers/tcgplayer' +import status from './status' // Glitchtip will only start if the DSN is set :D Sentry.init({ @@ -121,7 +121,7 @@ if (cluster.isPrimary) { // simple endpoint for monitoring server.get('/ping', (_, res) => { - res.status(200).end() + res.status(200).json({ ok: true }) }) server.use(express.static('./public')) diff --git a/server/src/interfaces.d.ts b/server/src/interfaces.d.ts index 922f7f71cc..2a6657e1c4 100644 --- a/server/src/interfaces.d.ts +++ b/server/src/interfaces.d.ts @@ -1,5 +1,7 @@ import { SupportedLanguages } from '@tcgdex/sdk' +export type Version = 'full' | 'brief' + export interface Pagination { page: number count: number diff --git a/server/src/libs/QueryEngine/filter.ts b/server/src/libs/QueryEngine/filter.ts index 524c5a4832..204e588adb 100644 --- a/server/src/libs/QueryEngine/filter.ts +++ b/server/src/libs/QueryEngine/filter.ts @@ -321,7 +321,12 @@ export function filterEntry(query: QueryList, item: T): boo // handle deeply nested items if ((key as string).includes('.')) { - value = objectGet(item, key as string) + try { + value = objectGet(item, key as string) + } catch (e) { + console.error(item, key) + throw e + } } // handle if nested item does not exists @@ -350,7 +355,7 @@ function filterValue(value: unknown, query: QueryValues // loop through each keys of the query // eslint-disable-next-line arrow-body-style return objectLoop(query as any, (querySubValue: unknown, queryKey: string) => { - return filterItem(value, {[queryKey]: querySubValue } as QueryValues) + return filterItem(value, { [queryKey]: querySubValue } as QueryValues) }) } diff --git a/server/src/libs/providers/cardmarket.ts b/server/src/libs/providers/cardmarket.ts index 73e21edf9b..3f8f54639b 100644 --- a/server/src/libs/providers/cardmarket.ts +++ b/server/src/libs/providers/cardmarket.ts @@ -1,26 +1,26 @@ import { objectOmit } from '@dzeio/object-util' interface Root { - version: number - createdAt: string - priceGuides: PriceGuide[] + version: number + createdAt: string + priceGuides: PriceGuide[] } interface PriceGuide { - idProduct: number - idCategory: number - avg?: number - low?: number - trend?: number - avg1?: number - avg7?: number - avg30?: number - "avg-holo"?: number - "low-holo"?: number - "trend-holo"?: number - "avg1-holo"?: number - "avg7-holo"?: number - "avg30-holo"?: number + idProduct: number + idCategory: number + avg?: number + low?: number + trend?: number + avg1?: number + avg7?: number + avg30?: number + "avg-holo"?: number + "low-holo"?: number + "trend-holo"?: number + "avg1-holo"?: number + "avg7-holo"?: number + "avg30-holo"?: number } const SUPPORTED_VERSION = 1 @@ -52,7 +52,7 @@ export async function updateDatas(): Promise { return true } -export async function getCardMarketPrice(card: { thirdParty: { cardmarket?: number }}): Promise { +export async function getCardMarketPrice(card: { thirdParty?: { cardmarket?: number } }): Promise { if (!dataCache || typeof card.thirdParty?.cardmarket !== 'number') { return null } diff --git a/server/src/libs/providers/tcgplayer/fallback.ts b/server/src/libs/providers/tcgplayer/fallback.ts index 2909299948..a15ffa363e 100644 --- a/server/src/libs/providers/tcgplayer/fallback.ts +++ b/server/src/libs/providers/tcgplayer/fallback.ts @@ -1,5 +1,5 @@ import { objectOmit } from '@dzeio/object-util' -import { sets } from '../../../V2/Components/Set' +import { langLists as sets } from '../../../V2/Components/Set' import TCGPlayer from './TCGPlayer' export interface Root { @@ -69,7 +69,7 @@ export async function updateTCGPlayerDatas(): Promise { return true } -export async function getTCGPlayerPrice(card: { thirdParty: { tcgplayer?: number } }): Promise<{ +export async function getTCGPlayerPrice(card: { thirdParty?: { tcgplayer?: number } }): Promise<{ unit: 'USD', updated: string normal?: Omit diff --git a/server/src/libs/providers/tcgplayer/index.ts b/server/src/libs/providers/tcgplayer/index.ts index 0ba969bc97..0469d1ac5d 100644 --- a/server/src/libs/providers/tcgplayer/index.ts +++ b/server/src/libs/providers/tcgplayer/index.ts @@ -19,7 +19,7 @@ export async function updateTCGPlayerDatas(): Promise { return source.updateTCGPlayerDatas() } -export async function getTCGPlayerPrice(card: { thirdParty: { tcgplayer?: number } }): Promise { +export async function getTCGPlayerPrice(card: { thirdParty?: { tcgplayer?: number } }): Promise { return source.getTCGPlayerPrice(card) } diff --git a/server/src/libs/providers/tcgplayer/official.ts b/server/src/libs/providers/tcgplayer/official.ts index 71a4a0c154..7549ee3a26 100644 --- a/server/src/libs/providers/tcgplayer/official.ts +++ b/server/src/libs/providers/tcgplayer/official.ts @@ -1,5 +1,5 @@ import { objectOmit } from '@dzeio/object-util' -import { sets } from '../../../V2/Components/Set' +import { langLists as sets } from '../../../V2/Components/Set' import TCGPlayer from './TCGPlayer' import list from './product-skus.mapping.json' @@ -48,7 +48,7 @@ export async function updateTCGPlayerDatas(): Promise { return true } -export async function getTCGPlayerPrice(card: { thirdParty: { tcgplayer?: number } }): Promise<{ +export async function getTCGPlayerPrice(card: { thirdParty?: { tcgplayer?: number } }): Promise<{ unit: 'USD', updated: string normal?: Omit diff --git a/server/src/status.ts b/server/src/status.ts index 2e56a8a81c..9a298a8b2a 100644 --- a/server/src/status.ts +++ b/server/src/status.ts @@ -1,47 +1,10 @@ -import { objectKeys, objectLoop, objectMap } from '@dzeio/object-util' +import { objectKeys, objectLoop, objectMap, objectSort } from '@dzeio/object-util' import express from 'express' 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' -import es from '../generated/es/stats.json' -import esMX from '../generated/es-mx/stats.json' -import fr from '../generated/fr/stats.json' -import id from '../generated/id/stats.json' -import it from '../generated/it/stats.json' -import ja from '../generated/ja/stats.json' -import ko from '../generated/ko/stats.json' -import nl from '../generated/nl/stats.json' -import pl from '../generated/pl/stats.json' -// import ptbr from '../generated/pt-br/stats.json' -import ptpt from '../generated/pt-pt/stats.json' -import pt from '../generated/pt/stats.json' -import ru from '../generated/ru/stats.json' -import th from '../generated/th/stats.json' -import zhcn from '../generated/zh-cn/stats.json' -import zhtw from '../generated/zh-tw/stats.json' +import stats from '../generated/stats.json' -const langs = { - 'zh-cn': zhcn, - 'zh-tw': zhtw, - 'nl': nl, - 'en': en, - 'fr': fr, - 'de': de, - 'id': id, - 'it': it, - 'ja': ja, - 'ko': ko, - 'pl': pl, - 'pt': pt, - // 'pt-br': ptbr, - 'pt-pt': ptpt, - 'ru': ru, - 'es': es, - 'th': th, - 'es-mx': esMX -} as const const langsToName = { 'zh-cn': 'Chinese (simplified)', @@ -62,9 +25,12 @@ const langsToName = { 'es': 'Spanish', 'th': 'Thai', 'es-mx': 'Spanish (Latin America)' - } as const +const langs = + objectSort(stats, objectKeys(langsToName)) + + /** * This file is meant to contains the TCGdex Project status page. */ @@ -125,9 +91,9 @@ objectLoop(langs, (stats, key) => preProcessSets(stats, key)) // Yes this is ugly export default express.Router() -.get('/github.svg', async (_, res): Promise => { - res.setHeader('Content-Type', 'image/svg+xml') - res.send(` + .get('/github.svg', async (_, res): Promise => { + res.setHeader('Content-Type', 'image/svg+xml') + res.send(` Dutch @@ -216,10 +182,10 @@ export default express.Router() ${langs['th'].count + langs['th'].images} of ${langs['th'].total * 2} (${(100 * (langs['th'].count + langs['th'].images) / (langs['th'].total * 2)).toFixed(2)}%) ${totalAsia.count + totalAsia.images} of ${totalAsia.total * 2} (${(100 * (totalAsia.count + totalAsia.images) / (totalAsia.total * 2)).toFixed(2)}%) `) -}) -.get('/', async (_, res): Promise => { + }) + .get('/', async (_, res): Promise => { - res.send(` + res.send(` @@ -364,47 +330,47 @@ export default express.Router() ${(await Promise.all(objectMap(serie, async (data, setId) => { - // loop through every sets + // loop through every sets - // find the set in the first available language (Should be English globally) - const setTotal = await findOneSet(data[0] as 'en', { id: setId }) - let str = '' + `${setTotal?.name} (${setId})
${setTotal?.cardCount.total ?? 1} cards` - // let str = '' + `${setId})` + // find the set in the first available language (Should be English globally) + const setTotal = await findOneSet(data[0] as 'en', { id: setId }) + let str = '' + `${setTotal?.name} (${setId})
${setTotal?.cardCount.total ?? 1} cards` + // let str = '' + `${setId})` - // Loop through every languages - const l = objectKeys(langs) - l.map((it) => { + // Loop through every languages + const l = objectKeys(langs) + l.map((it) => { - // Change the stats file depending on the language - let stats: any = langs[it] + // Change the stats file depending on the language + let stats: any = langs[it] - // Get the stats we want - const item = stats.sets[serieId]?.[setId] as {count: number, images: number} | undefined + // Get the stats we want + const item = stats.sets[serieId]?.[setId] as { count: number, images: number } | undefined - // if item dont exist for the language skip it - if (!item) { - str += `` - return - } + // if item dont exist for the language skip it + if (!item) { + str += `` + return + } - // Calculate percentages and status - const percent = 100 * item.count / (setTotal?.cardCount.total ?? 1) - const imgPercent = 100 * item.images / (setTotal?.cardCount.total ?? 1) - // const percent = 100 //100 * item.count / (setTotal?.cardCount.total ?? 1) - // const imgPercent = 100 //100 * item.images / (setTotal?.cardCount.total ?? 1) + // Calculate percentages and status + const percent = 100 * item.count / (setTotal?.cardCount.total ?? 1) + const imgPercent = 100 * item.images / (setTotal?.cardCount.total ?? 1) + // const percent = 100 //100 * item.count / (setTotal?.cardCount.total ?? 1) + // const imgPercent = 100 //100 * item.images / (setTotal?.cardCount.total ?? 1) - // append to string :D - str +=`${percent.toFixed(2)}%
(${item.count}) + // append to string :D + str += `${percent.toFixed(2)}%
(${item.count}) ${imgPercent.toFixed(2)}%
(${item.images})` - }) + }) - // finish Row - return str + '' - }))).join('')} + // finish Row + return str + '' + }))).join('')} `}))).join('')} `) -}) + })