mirror of
https://github.com/tcgdex/cards-database.git
synced 2025-08-16 09:08:52 +00:00
Compare commits
2 Commits
v2.15.4
...
decks-supp
Author | SHA1 | Date | |
---|---|---|---|
4908aeed8a | |||
2d427e7ddf |
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "1",
|
|
||||||
"name": "TCGdex",
|
|
||||||
"type": "collection",
|
|
||||||
"presets": {
|
|
||||||
"requestType": "http"
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,28 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Get the cards list
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{BASE_URL}}/v2/en/cards?sort:field=name&sort:order=DESC&pagination:page=1&pagination:itemsPerPage=4
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
query {
|
|
||||||
sort:field: name
|
|
||||||
sort:order: DESC
|
|
||||||
pagination:page: 1
|
|
||||||
pagination:itemsPerPage: 4
|
|
||||||
~name: furret
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
res.status: eq 200
|
|
||||||
res.body.length: eq 4
|
|
||||||
}
|
|
||||||
|
|
||||||
docs {
|
|
||||||
Fully describe the card list request, it also has every parameters it can
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Get one card
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{BASE_URL}}/v2/en/cards/swsh3-136
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
res.status: eq 200
|
|
||||||
res.body.id: eq swsh3-136
|
|
||||||
}
|
|
@@ -1,3 +0,0 @@
|
|||||||
vars {
|
|
||||||
BASE_URL: http://localhost:3000
|
|
||||||
}
|
|
@@ -1,3 +0,0 @@
|
|||||||
vars {
|
|
||||||
BASE_URL: https://api.tcgdex.net
|
|
||||||
}
|
|
@@ -1,22 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: 466 - Invalid Sorting
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{BASE_URL}}/v2/en/sets/swsh8/53
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
res.body.id: eq swsh8-53
|
|
||||||
res.status: eq 200
|
|
||||||
}
|
|
||||||
|
|
||||||
docs {
|
|
||||||
Validate the issue seen in
|
|
||||||
|
|
||||||
https://github.com/tcgdex/cards-database/issues/466
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: 467 - Validate that we can run OPTIONS
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
options {
|
|
||||||
url: {{BASE_URL}}/status
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
res.status: eq 200
|
|
||||||
}
|
|
@@ -1,22 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: 471 - Invalid Set Sorting
|
|
||||||
type: http
|
|
||||||
seq: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{BASE_URL}}/v2/en/sets/swsh12/10
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
res.body.id: eq swsh12-010
|
|
||||||
res.status: eq 200
|
|
||||||
}
|
|
||||||
|
|
||||||
docs {
|
|
||||||
Validate the issue seen in
|
|
||||||
|
|
||||||
https://github.com/tcgdex/cards-database/issues/471
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: 474 - Queries crashing the server
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{BASE_URL}}/v2/en/cards?legal.standard=true
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
query {
|
|
||||||
legal.standard: true
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
res.status: eq 200
|
|
||||||
}
|
|
||||||
|
|
||||||
docs {
|
|
||||||
Validate the issue seen in
|
|
||||||
|
|
||||||
https://github.com/tcgdex/cards-database/issues/474
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: 475 - Ability to query subfileds
|
|
||||||
type: http
|
|
||||||
seq: 4
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{BASE_URL}}/v2/en/cards?legal.standard=true
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
query {
|
|
||||||
legal.standard: true
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
res.status: eq 200
|
|
||||||
}
|
|
||||||
|
|
||||||
docs {
|
|
||||||
Validate the issue seen in
|
|
||||||
|
|
||||||
https://github.com/tcgdex/cards-database/issues/474
|
|
||||||
}
|
|
@@ -1,33 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: GraphQL API
|
|
||||||
type: graphql
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: {{BASE_URL}}/v2/graphql
|
|
||||||
body: graphql
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
body:graphql {
|
|
||||||
query Pouet {
|
|
||||||
cards {
|
|
||||||
id
|
|
||||||
localId
|
|
||||||
name
|
|
||||||
set {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
serie {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
res.status: eq 200
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Get a list of sets
|
|
||||||
type: http
|
|
||||||
seq: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{BASE_URL}}/v2/en/sets?sort:field=name&sort:order=DESC&pagination:page=1&pagination:itemsPerPage=1&name=Dark
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
query {
|
|
||||||
sort:field: name
|
|
||||||
sort:order: DESC
|
|
||||||
pagination:page: 1
|
|
||||||
pagination:itemsPerPage: 1
|
|
||||||
name: Dark
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
res.status: eq 200
|
|
||||||
res.body[0].id: eq swsh3
|
|
||||||
res.body.length: eq 1
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Get a set
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{BASE_URL}}/v2/en/sets/swsh3
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
res.status: eq 200
|
|
||||||
res.body.id: eq swsh3
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Get one card from a set
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: {{BASE_URL}}/v2/en/sets/swsh3/136
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
res.status: eq 200
|
|
||||||
res.body.id: eq swsh3-136
|
|
||||||
}
|
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
|
4
.github/workflows/conventionnal-commit.yml
vendored
4
.github/workflows/conventionnal-commit.yml
vendored
@@ -5,8 +5,8 @@ jobs:
|
|||||||
name: Conventional PR
|
name: Conventional PR
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v3
|
||||||
- uses: beemojs/conventional-pr-action@v2
|
- uses: beemojs/conventional-pr-action@v2
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
|
||||||
- name: Setup BunJS
|
- name: Setup BunJS
|
||||||
|
14
data/Base/Base Set 2/Decks/Grass Chopper.ts
Normal file
14
data/Base/Base Set 2/Decks/Grass Chopper.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Base Set 2"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base4.gc",
|
||||||
|
name: {
|
||||||
|
en: "Grass Chopper"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Fighting", "Grass"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
14
data/Base/Base Set 2/Decks/Hot Water.ts
Normal file
14
data/Base/Base Set 2/Decks/Hot Water.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Base Set 2"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base4.hw",
|
||||||
|
name: {
|
||||||
|
en: "Hot Water"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Fire", "Water"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
14
data/Base/Base Set 2/Decks/Lightning Bug.ts
Normal file
14
data/Base/Base Set 2/Decks/Lightning Bug.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Base Set 2"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base4.lg",
|
||||||
|
name: {
|
||||||
|
en: "Lightning Bug"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Lightning", "Grass"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
14
data/Base/Base Set 2/Decks/Psych Out.ts
Normal file
14
data/Base/Base Set 2/Decks/Psych Out.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Base Set 2"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base4.po",
|
||||||
|
name: {
|
||||||
|
en: "Psych Out"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Psychic", "Water"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
14
data/Base/Base Set/decks/Blackout.ts
Normal file
14
data/Base/Base Set/decks/Blackout.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Base Set"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base1.bl",
|
||||||
|
name: {
|
||||||
|
en: "Blackout"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Water", "Fighting"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
14
data/Base/Base Set/decks/Brushfire.ts
Normal file
14
data/Base/Base Set/decks/Brushfire.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Base Set"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base1.br",
|
||||||
|
name: {
|
||||||
|
en: "Brushfire"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Fire", "Grass"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
14
data/Base/Base Set/decks/Overgrowth.ts
Normal file
14
data/Base/Base Set/decks/Overgrowth.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Base Set"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base1.ov",
|
||||||
|
name: {
|
||||||
|
en: "Overgrowth"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Water", "Grass"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
14
data/Base/Base Set/decks/Zap!.ts
Normal file
14
data/Base/Base Set/decks/Zap!.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Base Set"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base1.za",
|
||||||
|
name: {
|
||||||
|
en: "Zap!"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Lightning", "Psychic"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
14
data/Base/Fossil/Decks/BodyGuard.ts
Normal file
14
data/Base/Fossil/Decks/BodyGuard.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Fossil"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base1.bd",
|
||||||
|
name: {
|
||||||
|
en: "BodyGuard"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Grass", "Fighting"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
14
data/Base/Fossil/Decks/LockDown.ts
Normal file
14
data/Base/Fossil/Decks/LockDown.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Base Set"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base1.ld",
|
||||||
|
name: {
|
||||||
|
en: "LockDown"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Fire", "Water"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
14
data/Base/Jungle/decks/Power Reserve.ts
Normal file
14
data/Base/Jungle/decks/Power Reserve.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Jungle"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base2.pr",
|
||||||
|
name: {
|
||||||
|
en: "Power Reserve"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Psychic", "Grass"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
14
data/Base/Jungle/decks/Water Blast.ts
Normal file
14
data/Base/Jungle/decks/Water Blast.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Jungle"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base2.wb",
|
||||||
|
name: {
|
||||||
|
en: "Water Blast"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Water", "Fighting"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
14
data/Base/Team Rocket/decks/Devastation.ts
Normal file
14
data/Base/Team Rocket/decks/Devastation.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Team Rocket"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base4.de",
|
||||||
|
name: {
|
||||||
|
en: "Devastation"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Grass", "Water"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
14
data/Base/Team Rocket/decks/Grass Chopper.ts
Normal file
14
data/Base/Team Rocket/decks/Grass Chopper.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Deck } from "../../../../interfaces"
|
||||||
|
import Set from "../../Team Rocket"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: "td.base4.to",
|
||||||
|
name: {
|
||||||
|
en: "Trouble"
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ["Psychic", "Grass"],
|
||||||
|
cards: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
40
data/Black & White/Noble Victories/Decks/Fast Daze.ts
Normal file
40
data/Black & White/Noble Victories/Decks/Fast Daze.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { Deck } from '../../../../interfaces'
|
||||||
|
import Set from "../../Noble Victories"
|
||||||
|
|
||||||
|
const deck: Deck = {
|
||||||
|
id: 'td.bw3.fd',
|
||||||
|
name: {
|
||||||
|
en: 'Fast Daze'
|
||||||
|
},
|
||||||
|
set: Set,
|
||||||
|
typesFocus: ['Fire', 'Grass'],
|
||||||
|
cards: [
|
||||||
|
{ id: 'bw3-12', variant: 'holo' },
|
||||||
|
'bw3-12',
|
||||||
|
{ id: 'bw3-11', quantity: 4 },
|
||||||
|
'bw3-3',
|
||||||
|
{ id: 'bw3-2', quantity: 2 },
|
||||||
|
{ id: 'bw3-1', quantity: 3 },
|
||||||
|
'bw3-5',
|
||||||
|
{ id: 'bw3-4', quantity: 2 },
|
||||||
|
{ id: 'bw3-7', quantity: 2 },
|
||||||
|
{ id: 'bw3-6', quantity: 2 },
|
||||||
|
{ id: 'bw3-9', quantity: 3 },
|
||||||
|
'bw3-21',
|
||||||
|
{ id: 'bw3-21', quantity: 2 },
|
||||||
|
{ id: 'bw3-17', quantity: 2 },
|
||||||
|
{ id: 'bw3-16', quantity: 2 },
|
||||||
|
{ id: 'bw3-7', quantity: 2 },
|
||||||
|
{ id: 'bw1-93', quantity: 2 },
|
||||||
|
{ id: 'bw1-100', quantity: 2 },
|
||||||
|
{ id: 'bw2-91', quantity: 2 },
|
||||||
|
{ id: 'bw2-91', quantity: 2 },
|
||||||
|
'bw3-92',
|
||||||
|
{ id: 'bw1-99', quantity: 2 },
|
||||||
|
{ id: 'bw3-95', quantity: 2 },
|
||||||
|
{ id: 'bw1-105', quantity: 12 },
|
||||||
|
{ id: 'bw1-106', quantity: 6 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deck
|
21
interfaces.d.ts
vendored
21
interfaces.d.ts
vendored
@@ -314,3 +314,24 @@ export interface Filter {
|
|||||||
cards: Array<string>
|
cards: Array<string>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Deck {
|
||||||
|
id: `td.${string}.${string}`
|
||||||
|
set: Set
|
||||||
|
name: Languages
|
||||||
|
typesFocus: Array<Types>
|
||||||
|
cards: Array<{
|
||||||
|
/**
|
||||||
|
* Card Global ID
|
||||||
|
*/
|
||||||
|
id: string
|
||||||
|
/**
|
||||||
|
* Card quantity in the deck
|
||||||
|
*/
|
||||||
|
quantity?: number
|
||||||
|
/**
|
||||||
|
* Card variant
|
||||||
|
*/
|
||||||
|
variant?: keyof Omit<variants, 'normal' | 'firstEdition'>
|
||||||
|
} | string>
|
||||||
|
}
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "bun compiler/index.ts",
|
"compile": "bun compiler/index.ts",
|
||||||
"dev": "bun --watch src/index.ts",
|
"dev": "bun --watch --hot src/index.ts",
|
||||||
"validate": "tsc --noEmit --project ./tsconfig.json",
|
"validate": "tsc --noEmit --project ./tsconfig.json",
|
||||||
"start": "bun src/index.ts"
|
"start": "bun src/index.ts"
|
||||||
},
|
},
|
||||||
|
@@ -69,7 +69,7 @@ export default class Card implements LocalCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static findOne(lang: SupportedLanguages, query: Query<SDKCard>) {
|
public static findOne(lang: SupportedLanguages, query: Query<SDKCard>) {
|
||||||
const res = handleSort(handleValidation(this.getAll(lang), query), query)
|
const res = handleValidation(this.getAll(lang), query)
|
||||||
if (res.length === 0) {
|
if (res.length === 0) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@@ -30,8 +30,8 @@ const endpointToField: Record<string, keyof SDKCard> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server
|
server
|
||||||
// Midleware that handle caching only in production and on GET requests
|
// Midleware that handle caching
|
||||||
.use(apicache.middleware('1 day', (req: Request) => process.env.NODE_ENV === 'production' && req.method === 'GET', {}))
|
.use(apicache.middleware('1 day', (req: Request) => req.method === 'GET', {}))
|
||||||
|
|
||||||
// .get('/cache/performance', (req, res) => {
|
// .get('/cache/performance', (req, res) => {
|
||||||
// res.json(apicache.getPerformance())
|
// res.json(apicache.getPerformance())
|
||||||
@@ -186,23 +186,23 @@ server
|
|||||||
let result: any | undefined
|
let result: any | undefined
|
||||||
switch (endpoint) {
|
switch (endpoint) {
|
||||||
case 'cards':
|
case 'cards':
|
||||||
result = Card.findOne(lang, { filters: { id }, strict: true })?.full()
|
result = Card.findOne(lang, { filters: { id }})?.full()
|
||||||
if (!result) {
|
if (!result) {
|
||||||
result = Card.findOne(lang, { filters: { name: id }, strict: true })?.full()
|
result = Card.findOne(lang, { filters: { name: id }})?.full()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'sets':
|
case 'sets':
|
||||||
result = Set.findOne(lang, { filters: { id }, strict: true })?.full()
|
result = Set.findOne(lang, { filters: { id }})?.full()
|
||||||
if (!result) {
|
if (!result) {
|
||||||
result = Set.findOne(lang, {filters: { name: id }, strict: true })?.full()
|
result = Set.findOne(lang, {filters: { name: id }})?.full()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'series':
|
case 'series':
|
||||||
result = Serie.findOne(lang, { filters: { id }, strict: true })?.full()
|
result = Serie.findOne(lang, { filters: { id }})?.full()
|
||||||
if (!result) {
|
if (!result) {
|
||||||
result = Serie.findOne(lang, { filters: { name: id }, strict: true })?.full()
|
result = Serie.findOne(lang, { filters: { name: id }})?.full()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
@@ -242,7 +242,7 @@ server
|
|||||||
switch (endpoint) {
|
switch (endpoint) {
|
||||||
case 'sets':
|
case 'sets':
|
||||||
result = Card
|
result = Card
|
||||||
.findOne(lang, { filters: { localId: subid, set: id }, strict: true})?.full()
|
.findOne(lang, { filters: { localId: subid, set: id }})?.full()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
@@ -8,11 +8,21 @@ const VERSION = 2
|
|||||||
// Init Express server
|
// Init Express server
|
||||||
const server = express()
|
const server = express()
|
||||||
|
|
||||||
|
// Set CORS global headers
|
||||||
|
server.use((_, res, next) => {
|
||||||
|
res
|
||||||
|
.setHeader('Access-Control-Allow-Origin', '*')
|
||||||
|
.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
|
||||||
|
.setHeader('Access-Control-Allow-Headers', 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range')
|
||||||
|
.setHeader('Access-Control-Expose-Headers', 'Content-Length,Content-Range')
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
|
||||||
// Route logging / Error logging for debugging
|
// Route logging / Error logging for debugging
|
||||||
server.use((req, res, next) => {
|
server.use((req, res, next) => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
// Date of request User-Agent 32 first chars request Method
|
// Date of request User-Agent 32 first chars request Method
|
||||||
let prefix = `\x1b[2m${now.toISOString()}\x1b[22m ${req.headers['user-agent']?.slice(0, 32).padEnd(32)} ${req.method.toUpperCase().padEnd(7)}`
|
let prefix = `\x1b[2m${now.toISOString()}\x1b[22m ${req.headers['user-agent']?.slice(0, 32).padEnd(32)} ${req.method.padEnd(7)}`
|
||||||
|
|
||||||
const url = new URL(req.url, `http://${req.headers.host}`)
|
const url = new URL(req.url, `http://${req.headers.host}`)
|
||||||
const fullURL = url.toString()
|
const fullURL = url.toString()
|
||||||
@@ -41,21 +51,6 @@ server.use((req, res, next) => {
|
|||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set CORS global headers
|
|
||||||
server.use((req, res, next) => {
|
|
||||||
res
|
|
||||||
.setHeader('Access-Control-Allow-Origin', '*')
|
|
||||||
.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
|
|
||||||
.setHeader('Access-Control-Allow-Headers', 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range')
|
|
||||||
.setHeader('Access-Control-Expose-Headers', 'Content-Length,Content-Range')
|
|
||||||
|
|
||||||
if (req.method.toUpperCase() === 'OPTIONS') {
|
|
||||||
res.status(200).send('ok')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
|
|
||||||
server.get('/', (_, res) => {
|
server.get('/', (_, res) => {
|
||||||
res.redirect('https://www.tcgdex.dev/?ref=api.tccgdex.net')
|
res.redirect('https://www.tcgdex.dev/?ref=api.tccgdex.net')
|
||||||
})
|
})
|
||||||
|
4
server/src/interfaces.d.ts
vendored
4
server/src/interfaces.d.ts
vendored
@@ -28,10 +28,6 @@ export interface Query<T extends {} = {}> {
|
|||||||
*/
|
*/
|
||||||
filters?: Partial<{ [Key in keyof T]: T[Key] extends object ? string | number | Array<string | number> : T[Key] | Array<T[Key]> }>
|
filters?: Partial<{ [Key in keyof T]: T[Key] extends object ? string | number | Array<string | number> : T[Key] | Array<T[Key]> }>
|
||||||
|
|
||||||
/**
|
|
||||||
* instead of filtering text search it will search using the full string
|
|
||||||
*/
|
|
||||||
strict?: boolean
|
|
||||||
/**
|
/**
|
||||||
* data sorting
|
* data sorting
|
||||||
*
|
*
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { mustBeObject, objectLoop } from '@dzeio/object-util'
|
import { objectLoop } from '@dzeio/object-util'
|
||||||
import { SupportedLanguages } from '@tcgdex/sdk'
|
import { SupportedLanguages } from '@tcgdex/sdk'
|
||||||
import { Response } from 'express'
|
import { Response } from 'express'
|
||||||
import { Query } from './interfaces'
|
import { Query } from './interfaces'
|
||||||
@@ -59,35 +59,23 @@ export function betterSorter(a: string, b: string) {
|
|||||||
*
|
*
|
||||||
* @param validator the validation object
|
* @param validator the validation object
|
||||||
* @param value the value to validate
|
* @param value the value to validate
|
||||||
* @param strict change how the results are fetched
|
|
||||||
* @returns `true` if valid else `false`
|
* @returns `true` if valid else `false`
|
||||||
*/
|
*/
|
||||||
export function validateItem(validator: any | Array<any>, value: any, strict: boolean = false): boolean {
|
export function validateItem(validator: any | Array<any>, value: any): boolean {
|
||||||
// convert to number is possible
|
|
||||||
if (/^\d+$/.test(validator)) {
|
|
||||||
validator = parseInt(validator)
|
|
||||||
}
|
|
||||||
|
|
||||||
// do a comparaison with null values
|
|
||||||
if (isNull(value)) {
|
|
||||||
return isNull(validator)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'object') {
|
if (typeof value === 'object') {
|
||||||
// invert signal so that an early exit mean that a match was found!
|
return objectLoop(value, (v) => {
|
||||||
return !objectLoop(value, (v) => {
|
|
||||||
// early exit to not infinitively loop through objects
|
// early exit to not infinitively loop through objects
|
||||||
if (typeof v === 'object') return true
|
if (typeof v === 'object') return true
|
||||||
|
|
||||||
// check for each childs until one match
|
// check for each childs
|
||||||
return !validateItem(validator, v, strict)
|
return validateItem(validator, v)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop to validate for an array
|
// loop to validate for an array
|
||||||
if (Array.isArray(validator)) {
|
if (Array.isArray(validator)) {
|
||||||
for (const sub of validator) {
|
for (const sub of validator) {
|
||||||
const res = validateItem(sub, value, strict)
|
const res = validateItem(sub, value)
|
||||||
if (res) {
|
if (res) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -95,12 +83,8 @@ export function validateItem(validator: any | Array<any>, value: any, strict: bo
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (typeof validator === 'string') {
|
if (typeof validator === 'string') {
|
||||||
// run a strict string validation
|
// run a string validation
|
||||||
if (strict) {
|
return value.toString().toLowerCase().includes(validator.toLowerCase())
|
||||||
return value.toString().toLowerCase() === validator.toLowerCase()
|
|
||||||
} else {
|
|
||||||
return value.toString().toLowerCase().includes(validator.toLowerCase())
|
|
||||||
}
|
|
||||||
} else if (typeof validator === 'number') {
|
} else if (typeof validator === 'number') {
|
||||||
// run a number validation
|
// run a number validation
|
||||||
if (typeof value === 'number') {
|
if (typeof value === 'number') {
|
||||||
@@ -120,16 +104,10 @@ export function validateItem(validator: any | Array<any>, value: any, strict: bo
|
|||||||
* @returns the sorted data
|
* @returns the sorted data
|
||||||
*/
|
*/
|
||||||
export function handleSort(data: Array<any>, query: Query<any>) {
|
export function handleSort(data: Array<any>, query: Query<any>) {
|
||||||
// handle when data has no entries
|
const sort: Query<any>['sort'] = query.sort ?? {field: 'id', order: 'ASC'}
|
||||||
if (data.length === 0) {
|
const field = sort.field
|
||||||
return data
|
const order = sort.order ?? 'ASC'
|
||||||
}
|
|
||||||
|
|
||||||
const defaultSortPriority = ['releaseDate', 'localId', 'id']
|
|
||||||
|
|
||||||
const firstEntry = data[0]
|
const firstEntry = data[0]
|
||||||
const field = query.sort?.field ?? defaultSortPriority.find((it) => it in firstEntry) ?? 'id'
|
|
||||||
const order = query.sort?.order ?? 'ASC'
|
|
||||||
|
|
||||||
// early exit if the order is not correctly set
|
// early exit if the order is not correctly set
|
||||||
if (order !== 'ASC' && order !== 'DESC') {
|
if (order !== 'ASC' && order !== 'DESC') {
|
||||||
@@ -138,43 +116,22 @@ export function handleSort(data: Array<any>, query: Query<any>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!(field in firstEntry)) {
|
if (!(field in firstEntry)) {
|
||||||
console.warn('can\'t sort using the field', field)
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
const sortType = typeof data[0][field]
|
||||||
return data.sort((a, b) => advSort(a[field], b[field], order))
|
if (sortType === 'number') {
|
||||||
}
|
if (order === 'ASC') {
|
||||||
|
return data.sort((a, b) => a[field] - b[field])
|
||||||
/**
|
} else {
|
||||||
*
|
return data.sort((a, b) => b[field] - a[field])
|
||||||
* @param order the base ordering
|
}
|
||||||
* @returns a function that is feed in the `sort` function
|
} else {
|
||||||
*/
|
if (order === 'ASC') {
|
||||||
const advSort = (a: string | number, b: string | number, order: 'ASC' | 'DESC' = 'ASC') => {
|
return data.sort((a, b) => a[field] > b[field] ? 1 : -1)
|
||||||
a = tryParse(a) ?? a
|
} else {
|
||||||
b = tryParse(b) ?? b
|
return data.sort((a, b) => a[field] > b[field] ? -1 : 1)
|
||||||
|
}
|
||||||
if (order === 'DESC') {
|
|
||||||
const tmp = a
|
|
||||||
a = b
|
|
||||||
b = tmp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof a === 'number' && typeof b === 'number') {
|
|
||||||
return a - b
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.toString().localeCompare(b.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryParse(value: string | number): number | null {
|
|
||||||
if (typeof value === 'number') {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
if (/^-?\d+$/.test(value)) {
|
|
||||||
return parseInt(value)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -185,7 +142,7 @@ function tryParse(value: string | number): number | null {
|
|||||||
* @returns the data that is in the paginated query
|
* @returns the data that is in the paginated query
|
||||||
*/
|
*/
|
||||||
export function handlePagination(data: Array<any>, query: Query<any>) {
|
export function handlePagination(data: Array<any>, query: Query<any>) {
|
||||||
if (!query.pagination || data.length === 0) {
|
if (!query.pagination) {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
const itemsPerPage = query.pagination.itemsPerPage ?? 100
|
const itemsPerPage = query.pagination.itemsPerPage ?? 100
|
||||||
@@ -211,47 +168,7 @@ export function handleValidation(data: Array<any>, query: Query) {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.filter((v) => objectLoop(filters, (valueToValidate, key: string) => {
|
return data.filter((v) => objectLoop(filters, (valueToValidate, key) => {
|
||||||
let value: any
|
return validateItem(valueToValidate, v[key])
|
||||||
// handle subfields
|
|
||||||
if (key.includes('.')) {
|
|
||||||
value = objectGet(v, key.split('.'))
|
|
||||||
} else {
|
|
||||||
value = v[key]
|
|
||||||
}
|
|
||||||
return validateItem(valueToValidate, value, query.strict)
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* go through an object to get a specific value
|
|
||||||
* @param obj the object to go through
|
|
||||||
* @param path the path to follow
|
|
||||||
* @returns the value or undefined
|
|
||||||
*/
|
|
||||||
function objectGet(obj: object, path: Array<string | number | symbol>): any | undefined {
|
|
||||||
mustBeObject(obj)
|
|
||||||
let pointer: object = obj;
|
|
||||||
for (let index = 0; index < path.length; index++) {
|
|
||||||
const key = path[index];
|
|
||||||
const nextIndex = index + 1;
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(pointer, key) && nextIndex < path.length) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
// if last index
|
|
||||||
if (nextIndex === path.length) {
|
|
||||||
return (pointer as any)[key]
|
|
||||||
}
|
|
||||||
// move pointer to new key
|
|
||||||
pointer = (pointer as any)[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* validate that the value is null or undefined
|
|
||||||
* @param value the value the check
|
|
||||||
* @returns if the value is undefined or null or not
|
|
||||||
*/
|
|
||||||
function isNull(value: any): value is (undefined | null) {
|
|
||||||
return typeof value === 'undefined' || value === null
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user