mirror of
https://github.com/tcgdex/cards-database.git
synced 2025-06-17 01:49:19 +00:00
feature: Implement new Server infrastructure with GraphQL. (#132)
* Added new compiler to db Signed-off-by: Avior <florian.bouillon@delta-wings.net> * Add compiled DB to artifacts Signed-off-by: Avior <florian.bouillon@delta-wings.net> * Fixed space error Signed-off-by: Avior <florian.bouillon@delta-wings.net> * Fixed? Signed-off-by: Avior <florian.bouillon@delta-wings.net> * Update node.js.yml * Update node.js.yml * Made change so the db is no longer dependent on the SDK Signed-off-by: Avior <florian.bouillon@delta-wings.net> * f Signed-off-by: Avior <florian.bouillon@delta-wings.net> * Fixed artifact Signed-off-by: Avior <florian.bouillon@delta-wings.net> * U Signed-off-by: Avior <florian.bouillon@delta-wings.net> * \Changed folder Signed-off-by: Avior <florian.bouillon@delta-wings.net> * Fixede? Signed-off-by: Avior <florian.bouillon@delta-wings.net> * Try with everything * saved the file ;) * ignore compiler * Fixed prebuild being run again * Fixed public folder Signed-off-by: Avior <github@avior.me> * fixed graphql file Signed-off-by: Avior <github@avior.me> * fixed? Signed-off-by: Avior <github@avior.me> * Check tree because life is potato Signed-off-by: Avior <github@avior.me> * this is harder Signed-off-by: Avior <github@avior.me> * f Signed-off-by: Avior <github@avior.me> * Fixed? Signed-off-by: Avior <github@avior.me> * r Signed-off-by: Avior <github@avior.me> * fd Signed-off-by: Avior <github@avior.me> * added back context Signed-off-by: Avior <github@avior.me> * ah Signed-off-by: Avior <github@avior.me> * AAH Signed-off-by: Avior <github@avior.me> * AAAH Signed-off-by: Avior <github@avior.me> * ffffffffffffffffff Signed-off-by: Avior <github@avior.me> * fix: Changed the default builder Signed-off-by: Avior <github@avior.me> * Removed useless tree function Signed-off-by: Avior <github@avior.me>
This commit is contained in:
112
server/src/V2/Components/Card.ts
Normal file
112
server/src/V2/Components/Card.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import { Card as SDKCard, CardResume, SupportedLanguages } from '@tcgdex/sdk'
|
||||
import Set from './Set'
|
||||
import { Pagination } from '../../interfaces'
|
||||
|
||||
type LocalCard = Omit<SDKCard, 'set'> & {set: () => Set}
|
||||
|
||||
interface variants {
|
||||
normal?: boolean;
|
||||
reverse?: boolean;
|
||||
holo?: boolean;
|
||||
firstEdition?: boolean;
|
||||
}
|
||||
|
||||
export default class Card implements LocalCard {
|
||||
illustrator?: string | undefined
|
||||
rarity!: string
|
||||
category!: string
|
||||
variants?: variants | undefined
|
||||
dexId?: number[] | undefined
|
||||
hp?: number | undefined
|
||||
types?: string[] | undefined
|
||||
evolveFrom?: string | undefined
|
||||
weight?: string | undefined
|
||||
description?: string | undefined
|
||||
level?: string | number | undefined
|
||||
stage?: string | undefined
|
||||
suffix?: string | undefined
|
||||
item?: { name: string; effect: string } | undefined
|
||||
abilities?: { type: string; name: string; effect: string }[] | undefined
|
||||
attacks?: { cost?: string[] | undefined; name: string; effect?: string | undefined; damage?: string | number | undefined }[] | undefined
|
||||
weaknesses?: { type: string; value?: string | undefined }[] | undefined
|
||||
resistances?: { type: string; value?: string | undefined }[] | undefined
|
||||
retreat?: number | undefined
|
||||
effect?: string | undefined
|
||||
trainerType?: string | undefined
|
||||
energyType?: string | undefined
|
||||
regulationMark?: string | undefined
|
||||
legal!: { standard: boolean; expanded: boolean }
|
||||
id!: string
|
||||
localId!: string
|
||||
name!: string
|
||||
image?: string | undefined
|
||||
|
||||
public constructor(
|
||||
private lang: SupportedLanguages,
|
||||
private card: SDKCard
|
||||
) {
|
||||
objectLoop(card, (it, key) => {
|
||||
if (key === 'set') {
|
||||
return
|
||||
}
|
||||
this[key as 'id'] = it
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
public set(): Set {
|
||||
return Set.findOne(this.lang, {id: this.card.set.id}) as Set
|
||||
}
|
||||
|
||||
public static find(lang: SupportedLanguages, params: Partial<Record<keyof SDKCard, any>> = {}, pagination?: Pagination) {
|
||||
let list : Array<SDKCard> = (require(`../../../generated/${lang}/cards.json`) as Array<SDKCard>)
|
||||
.filter((c) => objectLoop(params, (it, key) => {
|
||||
if (typeof it === "string") {
|
||||
return c[key as 'localId'].toLowerCase().includes(it.toLowerCase())
|
||||
}
|
||||
return c[key as 'localId'].includes(it)
|
||||
}))
|
||||
if (pagination) {
|
||||
list = list
|
||||
.splice(pagination.count * pagination.page - 1, pagination.count)
|
||||
}
|
||||
return list.map((it) => new Card(lang, it))
|
||||
}
|
||||
|
||||
public static raw(lang: SupportedLanguages): Array<SDKCard> {
|
||||
return require(`../../generated/${lang}/cards.json`)
|
||||
}
|
||||
|
||||
public static findOne(lang: SupportedLanguages, params: Partial<Record<keyof SDKCard, any>> = {}) {
|
||||
const res = (require(`../../generated/${lang}/cards.json`) as Array<SDKCard>).find((c) => {
|
||||
return objectLoop(params, (it, key) => {
|
||||
if (key === 'set') {
|
||||
return c['set'].id.includes(it) || c['set'].name.includes(it)
|
||||
}
|
||||
if (typeof it === "string") {
|
||||
return c[key as 'localId'].toLowerCase().includes(it.toLowerCase())
|
||||
}
|
||||
return c[key as 'localId'].includes(it)
|
||||
})
|
||||
})
|
||||
if (!res) {
|
||||
return undefined
|
||||
}
|
||||
return new Card(lang, res)
|
||||
}
|
||||
|
||||
public resume(): CardResume {
|
||||
return {
|
||||
id: this.id,
|
||||
localId: this.localId,
|
||||
name: this.name,
|
||||
image: this.image
|
||||
}
|
||||
}
|
||||
|
||||
public full(): SDKCard {
|
||||
return this.card
|
||||
}
|
||||
|
||||
}
|
66
server/src/V2/Components/Serie.ts
Normal file
66
server/src/V2/Components/Serie.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import { Serie as SDKSerie, SerieResume, SupportedLanguages } from '@tcgdex/sdk'
|
||||
import Set from './Set'
|
||||
import { Pagination } from '../../interfaces'
|
||||
|
||||
type LocalSerie = Omit<SDKSerie, 'sets'> & {sets: () => Array<Set>}
|
||||
|
||||
export default class Serie implements LocalSerie {
|
||||
|
||||
id!: string
|
||||
name!: string
|
||||
logo?: string | undefined
|
||||
|
||||
public constructor(
|
||||
private lang: SupportedLanguages,
|
||||
private serie: SDKSerie
|
||||
) {
|
||||
objectLoop(serie, (it, key) => {
|
||||
if (key === 'sets') {
|
||||
return
|
||||
}
|
||||
this[key as 'id'] = it
|
||||
})
|
||||
}
|
||||
|
||||
public sets(): Array<Set> {
|
||||
return this.serie.sets.map((s) => Set.findOne(this.lang, {id: s.id}) as Set)
|
||||
}
|
||||
|
||||
public static find(lang: SupportedLanguages, params: Partial<Record<keyof SDKSerie, any>> = {}, pagination?: Pagination) {
|
||||
let list = (require(`../../generated/${lang}/series.json`) as Array<SDKSerie>)
|
||||
.filter((c) => objectLoop(params, (it, key) => {
|
||||
return c[key as 'id'].includes(it)
|
||||
}))
|
||||
if (pagination) {
|
||||
list = list
|
||||
.splice(pagination.count * pagination.page - 1, pagination.count)
|
||||
}
|
||||
return list.map((it) => new Serie(lang, it))
|
||||
}
|
||||
|
||||
public static findOne(lang: SupportedLanguages, params: Partial<Record<keyof Serie, any>> = {}): Serie | undefined {
|
||||
const res = (require(`../../../generated/${lang}/series.json`) as Array<SDKSerie>)
|
||||
.find((c) => {
|
||||
return objectLoop(params, (it, key) => {
|
||||
return c[key as 'id'].includes(it)
|
||||
})
|
||||
})
|
||||
if (!res) {
|
||||
return undefined
|
||||
}
|
||||
return new Serie(lang, res)
|
||||
}
|
||||
|
||||
public resume(): SerieResume {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
logo: this.logo
|
||||
}
|
||||
}
|
||||
|
||||
public full(): SDKSerie {
|
||||
return this.serie
|
||||
}
|
||||
}
|
89
server/src/V2/Components/Set.ts
Normal file
89
server/src/V2/Components/Set.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import { Set as SDKSet, SetResume, SupportedLanguages } from '@tcgdex/sdk'
|
||||
import Card from './Card'
|
||||
import { Pagination } from '../../interfaces'
|
||||
import Serie from './Serie'
|
||||
|
||||
interface variants {
|
||||
normal?: boolean;
|
||||
reverse?: boolean;
|
||||
holo?: boolean;
|
||||
firstEdition?: boolean;
|
||||
}
|
||||
|
||||
type LocalSet = {serie: () => Serie, cards: () => Array<Card>} & Omit<SDKSet, 'serie' | 'cards'>
|
||||
|
||||
export default class Set implements LocalSet {
|
||||
|
||||
public constructor(
|
||||
private lang: SupportedLanguages,
|
||||
private set: SDKSet
|
||||
) {
|
||||
objectLoop(set, (it, key) => {
|
||||
if (key === 'serie' || key === 'cards') {
|
||||
return
|
||||
}
|
||||
this[key as 'id'] = it
|
||||
})
|
||||
}
|
||||
|
||||
tcgOnline?: string | undefined
|
||||
variants?: variants | undefined
|
||||
releaseDate!: string
|
||||
legal!: { standard: boolean; expanded: boolean }
|
||||
cardCount!: { total: number; official: number; normal: number; reverse: number; holo: number; firstEd?: number | undefined }
|
||||
id!: string
|
||||
name!: string
|
||||
logo?: string | undefined
|
||||
symbol?: string | undefined
|
||||
|
||||
public serie(): Serie {
|
||||
return Serie.findOne(this.lang, {id: this.set.serie.id}) as Serie
|
||||
}
|
||||
|
||||
public cards(): Array<Card> {
|
||||
return this.set.cards.map((s) => Card.findOne(this.lang, {id: s.id}) as Card)
|
||||
}
|
||||
|
||||
public static find(lang: SupportedLanguages, params: Partial<Record<keyof SDKSet, any>> = {}, pagination?: Pagination) {
|
||||
let list = (require(`../../../generated/${lang}/sets.json`) as Array<SDKSet>)
|
||||
.filter((c) => objectLoop(params, (it, key) => {
|
||||
return c[key as 'id'].includes(it)
|
||||
}))
|
||||
if (pagination) {
|
||||
list = list
|
||||
.splice(pagination.count * pagination.page - 1, pagination.count)
|
||||
}
|
||||
return list.map((it) => new Set(lang, it))
|
||||
}
|
||||
|
||||
public static findOne(lang: SupportedLanguages, params: Partial<Record<keyof Set, any>> = {}) {
|
||||
const res = (require(`../../../generated/${lang}/sets.json`) as Array<SDKSet>).find((c) => {
|
||||
return objectLoop(params, (it, key) => {
|
||||
return c[key as 'id'].includes(it)
|
||||
})
|
||||
})
|
||||
if (!res) {
|
||||
return undefined
|
||||
}
|
||||
return new Set(lang, res)
|
||||
}
|
||||
|
||||
public resume(): SetResume {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
logo: this.logo,
|
||||
symbol: this.symbol,
|
||||
cardCount: {
|
||||
total: this.cardCount.total,
|
||||
official: this.cardCount.official
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public full(): SDKSet {
|
||||
return this.set
|
||||
}
|
||||
|
||||
}
|
212
server/src/V2/endpoints/jsonEndpoints.ts
Normal file
212
server/src/V2/endpoints/jsonEndpoints.ts
Normal file
@ -0,0 +1,212 @@
|
||||
import { objectKeys, objectSize } from '@dzeio/object-util'
|
||||
import { Card as SDKCard } from '@tcgdex/sdk'
|
||||
import Card from '../Components/Card'
|
||||
import Serie from '../Components/Serie'
|
||||
import Set from '../Components/Set'
|
||||
import express from 'express'
|
||||
import apicache from 'apicache'
|
||||
import { betterSorter, checkLanguage, sendError, unique } from '../../util'
|
||||
|
||||
const server = express.Router()
|
||||
|
||||
const endpointToField: Record<string, keyof SDKCard> = {
|
||||
"categories": 'category',
|
||||
'energy-types': 'energyType',
|
||||
"hp": 'hp',
|
||||
'illustrators': 'illustrator',
|
||||
"rarities": 'rarity',
|
||||
'regulation-marks': 'regulationMark',
|
||||
"retreats": 'retreat',
|
||||
"stages": "stage",
|
||||
"suffixes": "suffix",
|
||||
"trainer-types": "trainerType",
|
||||
|
||||
// fields that need special care
|
||||
'dex-ids': 'dexId',
|
||||
"sets": "set",
|
||||
"types": "types",
|
||||
"variants": "variants",
|
||||
}
|
||||
|
||||
// server
|
||||
// .get('/cache/performance', (req, res) => {
|
||||
// res.json(apicache.getPerformance())
|
||||
// })
|
||||
|
||||
// // add route to display cache index
|
||||
// .get('/cache/index', (req, res) => {
|
||||
// res.json(apicache.getIndex())
|
||||
// })
|
||||
|
||||
server
|
||||
.use(apicache.middleware('1 day', undefined, {}))
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Listing Endpoint
|
||||
* ex: /v2/en/cards
|
||||
*/
|
||||
.get('/:lang/:endpoint', (req, res): void => {
|
||||
let { lang, endpoint } = req.params
|
||||
|
||||
if (endpoint.endsWith('.json')) {
|
||||
endpoint = endpoint.replace('.json', '')
|
||||
}
|
||||
|
||||
if (!checkLanguage(lang)) {
|
||||
return sendError('LanguageNotFoundError', res, lang)
|
||||
}
|
||||
|
||||
let result: any
|
||||
|
||||
switch (endpoint) {
|
||||
case 'cards':
|
||||
// to be quicker we directly return the raw file
|
||||
if (objectSize(req.query) === 0) {
|
||||
result = Card.raw(lang)
|
||||
return
|
||||
} else {
|
||||
result = Card
|
||||
.find(lang, req.query)
|
||||
.map((c) => c.resume())
|
||||
}
|
||||
break
|
||||
|
||||
case 'sets':
|
||||
result = Set
|
||||
.find(lang, req.query)
|
||||
.map((c) => c.resume())
|
||||
break
|
||||
case 'series':
|
||||
result = Serie
|
||||
.find(lang, req.query)
|
||||
.map((c) => c.resume())
|
||||
break
|
||||
case 'categories':
|
||||
case "energy-types":
|
||||
case "hp":
|
||||
case "illustrators":
|
||||
case "rarities":
|
||||
case "regulation-marks":
|
||||
case "retreats":
|
||||
case "series":
|
||||
case "stages":
|
||||
case "suffixes":
|
||||
case "trainer-types":
|
||||
result = unique(
|
||||
Card.raw(lang)
|
||||
.map((c) => c[endpointToField[endpoint]] as string)
|
||||
.filter((c) => c)
|
||||
).sort(betterSorter)
|
||||
break
|
||||
case "types":
|
||||
case "dex-ids":
|
||||
result = unique(
|
||||
Card.raw(lang)
|
||||
.map((c) => c[endpointToField[endpoint]] as Array<string>)
|
||||
.filter((c) => c)
|
||||
.reduce((p, c) => [...p, ...c], [] as Array<string>)
|
||||
).sort(betterSorter)
|
||||
break
|
||||
case "variants":
|
||||
result = unique(
|
||||
Card.raw(lang)
|
||||
.map((c) => objectKeys(c.variants ?? {}) as Array<string>)
|
||||
.filter((c) => c)
|
||||
.reduce((p, c) => [...p, ...c], [] as Array<string>)
|
||||
).sort()
|
||||
break
|
||||
default:
|
||||
sendError('EndpointNotFoundError', res, endpoint)
|
||||
return
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
sendError('NotFoundError', res)
|
||||
}
|
||||
res.json(result)
|
||||
})
|
||||
|
||||
/**
|
||||
* Listing Endpoint
|
||||
* ex: /v2/en/cards/base1-1
|
||||
*/
|
||||
.get('/:lang/:endpoint/:id', (req, res) => {
|
||||
let { id, lang, endpoint } = req.params
|
||||
|
||||
if (id.endsWith('.json')) {
|
||||
id = id.replace('.json', '')
|
||||
}
|
||||
|
||||
id = id.toLowerCase()
|
||||
|
||||
if (!checkLanguage(lang)) {
|
||||
return sendError('LanguageNotFoundError', res, lang)
|
||||
}
|
||||
|
||||
let result: any | undefined
|
||||
switch (endpoint) {
|
||||
case 'cards':
|
||||
result = Card.findOne(lang, {id})?.full()
|
||||
if (!result) {
|
||||
result = Card.findOne(lang, {name: id})?.full()
|
||||
}
|
||||
break
|
||||
|
||||
case 'sets':
|
||||
result = Set.findOne(lang, {id})?.full()
|
||||
if (!result) {
|
||||
result = Set.findOne(lang, {name: id})?.full()
|
||||
}
|
||||
break
|
||||
|
||||
case 'series':
|
||||
result = Serie.findOne(lang, {id})?.full()
|
||||
if (!result) {
|
||||
result = Serie.findOne(lang, {name: id})?.full()
|
||||
}
|
||||
break
|
||||
default:
|
||||
result = Card.find(lang, {[endpointToField[endpoint]]: id})
|
||||
}
|
||||
if (!result) {
|
||||
return res.status(404).send({error: "Endpoint or id not found"})
|
||||
}
|
||||
return res.send(result)
|
||||
|
||||
})
|
||||
|
||||
/**
|
||||
* sub id Endpoint (for the set endpoint only currently)
|
||||
* ex: /v2/en/sets/base1/1
|
||||
*/
|
||||
.get('/:lang/:endpoint/:id/:subid', (req, res) => {
|
||||
let { id, lang, endpoint, subid } = req.params
|
||||
|
||||
if (subid.endsWith('.json')) {
|
||||
subid = subid.replace('.json', '')
|
||||
}
|
||||
|
||||
id = id.toLowerCase()
|
||||
subid = subid.toLowerCase()
|
||||
|
||||
if (!checkLanguage(lang)) {
|
||||
return sendError('LanguageNotFoundError', res, lang)
|
||||
}
|
||||
|
||||
let result: any | undefined
|
||||
|
||||
switch (endpoint) {
|
||||
case 'sets':
|
||||
result = Card
|
||||
.findOne(lang, {localId: subid, set: id})?.full()
|
||||
break
|
||||
}
|
||||
if (!result) {
|
||||
return sendError('NotFoundError', res)
|
||||
}
|
||||
return res.send(result)
|
||||
})
|
||||
|
||||
export default server
|
22
server/src/V2/graphql/index.ts
Normal file
22
server/src/V2/graphql/index.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import express from 'express'
|
||||
import { graphqlHTTP } from 'express-graphql'
|
||||
import { buildSchema } from 'graphql'
|
||||
import resolver from './resolver'
|
||||
import fs from 'fs'
|
||||
|
||||
// Init Express Router
|
||||
const router = express.Router()
|
||||
/**
|
||||
* Drawbacks
|
||||
* Attack.damage is a string instead of possibly being a number or a string
|
||||
*/
|
||||
const schema = buildSchema(fs.readFileSync('./public/v2/graphql.gql').toString())
|
||||
|
||||
// Add graphql to the route
|
||||
router.use(graphqlHTTP({
|
||||
schema,
|
||||
rootValue: resolver,
|
||||
graphiql: true
|
||||
}))
|
||||
|
||||
export default router
|
49
server/src/V2/graphql/resolver.ts
Normal file
49
server/src/V2/graphql/resolver.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import Card from '../Components/Card'
|
||||
import { Options } from '../../interfaces'
|
||||
import Serie from '../Components/Serie'
|
||||
import Set from '../Components/Set'
|
||||
import { SupportedLanguages } from '@tcgdex/sdk'
|
||||
import { checkLanguage } from '../../util'
|
||||
|
||||
const middleware = <Q extends Record<string, any> = Record<string, any>>(fn: (lang: SupportedLanguages, query: Q) => any) => (
|
||||
data: Q,
|
||||
_: any,
|
||||
e: any
|
||||
) => {
|
||||
let lang = e?.fieldNodes?.[0]?.directives?.[0]?.arguments?.[0]?.value?.value
|
||||
if (!lang) {
|
||||
lang = 'en'
|
||||
}
|
||||
if (!checkLanguage(lang)) {
|
||||
return undefined
|
||||
}
|
||||
return fn(lang, data)
|
||||
}
|
||||
|
||||
export default {
|
||||
// Cards Endpoints
|
||||
cards: middleware<Options<keyof Card['card']>>((lang, query) => {
|
||||
return Card.find(lang, query.filters ?? {}, query.pagination)
|
||||
}),
|
||||
card: middleware<{set?: string, id: string}>((lang, query) => {
|
||||
const toSearch = query.set ? 'localId' : 'id'
|
||||
return Card.findOne(lang, {[toSearch]: query.id})
|
||||
}),
|
||||
|
||||
// Set Endpoints
|
||||
set: middleware<{id: string}>((lang, query) => {
|
||||
return Set.findOne(lang, {id: query.id}) ?? Set.findOne(lang, {name: query.id})
|
||||
}),
|
||||
sets: middleware<Options<keyof Set['set']>>((lang, query) => {
|
||||
return Set.find(lang, query.filters ?? {}, query.pagination)
|
||||
}),
|
||||
|
||||
// Serie Endpoints
|
||||
serie: middleware<{id: string}>((lang, query) => {
|
||||
return Serie.findOne(lang, {id: query.id}) ?? Serie.findOne(lang, {name: query.id})
|
||||
}),
|
||||
series: middleware<Options<keyof Serie['serie']>>((lang, query) => {
|
||||
return Serie.find(lang, query.filters ?? {}, query.pagination)
|
||||
}),
|
||||
|
||||
};
|
35
server/src/index.ts
Normal file
35
server/src/index.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import express from 'express'
|
||||
import graphql from './V2/graphql'
|
||||
import jsonEndpoints from './V2/endpoints/jsonEndpoints'
|
||||
|
||||
// Current API version
|
||||
const VERSION = 2
|
||||
|
||||
// Init Express server
|
||||
const server = express()
|
||||
|
||||
// Set 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()
|
||||
})
|
||||
|
||||
server.get('/', (_, res) => {
|
||||
res.redirect('https://www.tcgdex.net/docs?ref=api.tcgdex.net')
|
||||
})
|
||||
|
||||
server.use(express.static('./public'))
|
||||
|
||||
// Setup GraphQL
|
||||
server.use(`/v${VERSION}/graphql`, graphql)
|
||||
|
||||
// Setup JSON endpoints
|
||||
server.use(`/v${VERSION}`, jsonEndpoints)
|
||||
|
||||
// Start server
|
||||
server.listen(3000)
|
||||
console.log(`🚀 Server ready at localhost:3000`);
|
11
server/src/interfaces.d.ts
vendored
Normal file
11
server/src/interfaces.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
import { SupportedLanguages } from '@tcgdex/sdk'
|
||||
|
||||
export interface Pagination {
|
||||
page: number
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface Options<T> {
|
||||
pagination?: Pagination
|
||||
filters?: Partial<Record<T, any>>
|
||||
}
|
57
server/src/util.ts
Normal file
57
server/src/util.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { SupportedLanguages } from '@tcgdex/sdk'
|
||||
import { Response } from 'express'
|
||||
import fs from 'fs'
|
||||
|
||||
export function checkLanguage(str: string): str is SupportedLanguages {
|
||||
return ['en', 'fr', 'es', 'it', 'pt', 'de'].includes(str)
|
||||
}
|
||||
|
||||
export function unique(arr: Array<string>): Array<string> {
|
||||
return arr.reduce((p, c) => p.includes(c) ? p : [...p, c], [] as Array<string>)
|
||||
}
|
||||
|
||||
export function sendError(error: 'UnknownError' | 'NotFoundError' | 'LanguageNotFoundError' | 'EndpointNotFoundError', res: Response, v?: any) {
|
||||
let message = ''
|
||||
let status = 404
|
||||
switch (error) {
|
||||
case 'LanguageNotFoundError':
|
||||
message = `Language not found (${v})`
|
||||
break
|
||||
case 'EndpointNotFoundError':
|
||||
message = `Endpoint not found (${v})`
|
||||
break
|
||||
case 'NotFoundError':
|
||||
message = 'The resource you are searching does not exists'
|
||||
break
|
||||
case 'UnknownError':
|
||||
default:
|
||||
message = `an unknown error occured (${v})`
|
||||
status = 500
|
||||
break
|
||||
}
|
||||
res.status(status).json({
|
||||
message
|
||||
}).end()
|
||||
|
||||
}
|
||||
|
||||
export function betterSorter(a: string, b: string) {
|
||||
const ra = parseInt(a, 10)
|
||||
const rb = parseInt(b, 10)
|
||||
if (!isNaN(ra) && !isNaN(rb)) {
|
||||
return ra - rb
|
||||
}
|
||||
return a >= b ? 1 : -1
|
||||
}
|
||||
|
||||
export function tree(path: string, padding = 0) {
|
||||
const folder = fs.readdirSync(path)
|
||||
for (const file of folder) {
|
||||
const filePath = path + '/' + file
|
||||
console.log(filePath.padStart(padding, '-'))
|
||||
try {
|
||||
fs.lstatSync(filePath).isDirectory()
|
||||
tree(filePath)
|
||||
} catch {}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user