mirror of
https://github.com/tcgdex/cards-database.git
synced 2025-06-28 23:09:18 +00:00
feat: Add better sorting/filtering/pagination (#458)
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import { Card as SDKCard, CardResume, SupportedLanguages } from '@tcgdex/sdk'
|
||||
import { Pagination } from '../../interfaces'
|
||||
import { lightCheck } from '../../util'
|
||||
import { CardResume, Card as SDKCard, SupportedLanguages } from '@tcgdex/sdk'
|
||||
import { Query } from '../../interfaces'
|
||||
import { handlePagination, handleSort, handleValidation } from '../../util'
|
||||
import Set from './Set'
|
||||
|
||||
type LocalCard = Omit<SDKCard, 'set'> & {set: () => Set}
|
||||
@ -55,40 +55,25 @@ export default class Card implements LocalCard {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
public set(): Set {
|
||||
return Set.findOne(this.lang, {id: this.card.set.id}) as Set
|
||||
return Set.findOne(this.lang, {filters: { 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) => {
|
||||
return lightCheck(c[key as 'localId'], 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> {
|
||||
public static getAll(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' && typeof it === 'string') {
|
||||
return (c['set'].id === it || lightCheck(c['set'].name, it))
|
||||
}
|
||||
return lightCheck(c[key as 'localId'], it)
|
||||
})
|
||||
})
|
||||
if (!res) {
|
||||
public static find(lang: SupportedLanguages, query: Query<SDKCard>) {
|
||||
return handlePagination(handleSort(handleValidation(this.getAll(lang), query), query), query)
|
||||
.map((it) => new Card(lang, it))
|
||||
}
|
||||
|
||||
public static findOne(lang: SupportedLanguages, query: Query<SDKCard>) {
|
||||
const res = handleValidation(this.getAll(lang), query)
|
||||
if (res.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
return new Card(lang, res)
|
||||
return new Card(lang, res[0])
|
||||
}
|
||||
|
||||
public resume(): CardResume {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import { Serie as SDKSerie, SerieResume, SupportedLanguages } from '@tcgdex/sdk'
|
||||
import { Pagination } from '../../interfaces'
|
||||
import { lightCheck } from '../../util'
|
||||
import { Query } from '../../interfaces'
|
||||
import { handlePagination, handleSort, handleValidation } from '../../util'
|
||||
import Set from './Set'
|
||||
|
||||
type LocalSerie = Omit<SDKSerie, 'sets'> & {sets: () => Array<Set>}
|
||||
@ -25,34 +25,24 @@ export default class Serie implements LocalSerie {
|
||||
}
|
||||
|
||||
public sets(): Array<Set> {
|
||||
return this.serie.sets.map((s) => Set.findOne(this.lang, {id: s.id}) as Set)
|
||||
return this.serie.sets.map((s) => Set.findOne(this.lang, {filters: { 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) => {
|
||||
if (key === 'id') return c[key] === it
|
||||
return lightCheck(c[key as 'id'], it)
|
||||
}))
|
||||
if (pagination) {
|
||||
list = list
|
||||
.splice(pagination.count * pagination.page - 1, pagination.count)
|
||||
}
|
||||
return list.map((it) => new Serie(lang, it))
|
||||
public static getAll(lang: SupportedLanguages): Array<SDKSerie> {
|
||||
return require(`../../../generated/${lang}/series.json`)
|
||||
}
|
||||
|
||||
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) => {
|
||||
if (key === 'id') return c[key] === it
|
||||
return lightCheck(c[key as 'id'], it)
|
||||
})
|
||||
})
|
||||
if (!res) {
|
||||
public static find(lang: SupportedLanguages, query: Query<SDKSerie>) {
|
||||
return handlePagination(handleSort(handleValidation(this.getAll(lang), query), query), query)
|
||||
.map((it) => new Serie(lang, it))
|
||||
}
|
||||
|
||||
public static findOne(lang: SupportedLanguages, query: Query<SDKSerie>) {
|
||||
const res = handleValidation(this.getAll(lang), query)
|
||||
if (res.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
return new Serie(lang, res)
|
||||
return new Serie(lang, res[0])
|
||||
}
|
||||
|
||||
public resume(): SerieResume {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { objectLoop } from '@dzeio/object-util'
|
||||
import { Set as SDKSet, SetResume, SupportedLanguages } from '@tcgdex/sdk'
|
||||
import { Pagination } from '../../interfaces'
|
||||
import { lightCheck } from '../../util'
|
||||
import { Query } from '../../interfaces'
|
||||
import { handlePagination, handleSort, handleValidation } from '../../util'
|
||||
import Card from './Card'
|
||||
import Serie from './Serie'
|
||||
|
||||
@ -39,45 +39,28 @@ export default class Set implements LocalSet {
|
||||
symbol?: string | undefined
|
||||
|
||||
public serie(): Serie {
|
||||
return Serie.findOne(this.lang, {id: this.set.serie.id}) as Serie
|
||||
return Serie.findOne(this.lang, {filters: { 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)
|
||||
return this.set.cards.map((s) => Card.findOne(this.lang, { filters: { 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) => {
|
||||
if (key === 'id' || key === 'name') {
|
||||
return c[key as 'id'].toLowerCase() === it.toLowerCase()
|
||||
} else if (typeof it === 'string') {
|
||||
return c[key as 'id'].toLowerCase().includes(it.toLowerCase())
|
||||
}
|
||||
return lightCheck(c[key as 'id'], it)
|
||||
}))
|
||||
if (pagination) {
|
||||
list = list
|
||||
.splice(pagination.count * pagination.page - 1, pagination.count)
|
||||
}
|
||||
return list.map((it) => new Set(lang, it))
|
||||
public static getAll(lang: SupportedLanguages): Array<SDKSet> {
|
||||
return require(`../../../generated/${lang}/sets.json`)
|
||||
}
|
||||
|
||||
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) => {
|
||||
if (key === 'id' || key === 'name') {
|
||||
return c[key as 'id'].toLowerCase() === it.toLowerCase()
|
||||
} else if (typeof it === 'string') {
|
||||
return c[key as 'id'].toLowerCase().includes(it.toLowerCase())
|
||||
}
|
||||
return lightCheck(c[key as 'id'], it)
|
||||
})
|
||||
})
|
||||
if (!res) {
|
||||
public static find(lang: SupportedLanguages, query: Query<SDKSet>) {
|
||||
return handlePagination(handleSort(handleValidation(this.getAll(lang), query), query), query)
|
||||
.map((it) => new Set(lang, it))
|
||||
}
|
||||
|
||||
public static findOne(lang: SupportedLanguages, query: Query<SDKSet>) {
|
||||
const res = handleValidation(this.getAll(lang), query)
|
||||
if (res.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
return new Set(lang, res)
|
||||
return new Set(lang, res[0])
|
||||
}
|
||||
|
||||
public resume(): SetResume {
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { objectKeys } from '@dzeio/object-util'
|
||||
import { objectKeys, objectLoop } from '@dzeio/object-util'
|
||||
import { Card as SDKCard } from '@tcgdex/sdk'
|
||||
import apicache from 'apicache'
|
||||
import express from 'express'
|
||||
import { Query } from '../../interfaces'
|
||||
import { betterSorter, checkLanguage, sendError, unique } from '../../util'
|
||||
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()
|
||||
|
||||
@ -48,6 +49,40 @@ server
|
||||
next()
|
||||
})
|
||||
|
||||
// handle Query builder
|
||||
.use((req, _, next) => {
|
||||
// handle no query
|
||||
if (!req.query) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
const items: Query = {
|
||||
filters: undefined,
|
||||
sort: undefined,
|
||||
pagination: undefined
|
||||
}
|
||||
|
||||
objectLoop(req.query as Record<string, string | Array<string>>, (value: string | Array<string>, key: string) => {
|
||||
if (!key.includes(':')) {
|
||||
key = 'filters:' + key
|
||||
}
|
||||
const [cat, item] = key.split(':', 2) as ['filters', string]
|
||||
if (!items[cat]) {
|
||||
items[cat] = {}
|
||||
}
|
||||
const finalValue = Array.isArray(value) ? value.map((it) => isNaN(parseInt(it)) ? it : parseInt(it)) : isNaN(parseInt(value)) ? value : parseInt(value)
|
||||
// @ts-expect-error normal behavior
|
||||
items[cat][item] = finalValue
|
||||
|
||||
})
|
||||
console.log(items)
|
||||
// @ts-expect-error normal behavior
|
||||
req.advQuery = items
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* Listing Endpoint
|
||||
@ -56,6 +91,9 @@ server
|
||||
.get('/:lang/:endpoint', (req, res): void => {
|
||||
let { lang, endpoint } = req.params
|
||||
|
||||
// @ts-expect-error normal behavior
|
||||
const query: Query = req.advQuery
|
||||
|
||||
if (endpoint.endsWith('.json')) {
|
||||
endpoint = endpoint.replace('.json', '')
|
||||
}
|
||||
@ -69,18 +107,18 @@ server
|
||||
switch (endpoint) {
|
||||
case 'cards':
|
||||
result = Card
|
||||
.find(lang, req.query)
|
||||
.find(lang, query)
|
||||
.map((c) => c.resume())
|
||||
break
|
||||
|
||||
case 'sets':
|
||||
result = Set
|
||||
.find(lang, req.query)
|
||||
.find(lang, query)
|
||||
.map((c) => c.resume())
|
||||
break
|
||||
case 'series':
|
||||
result = Serie
|
||||
.find(lang, req.query)
|
||||
.find(lang, query)
|
||||
.map((c) => c.resume())
|
||||
break
|
||||
case 'categories':
|
||||
@ -95,7 +133,7 @@ server
|
||||
case "suffixes":
|
||||
case "trainer-types":
|
||||
result = unique(
|
||||
Card.raw(lang)
|
||||
Card.getAll(lang)
|
||||
.map((c) => c[endpointToField[endpoint]] as string)
|
||||
.filter((c) => c)
|
||||
).sort(betterSorter)
|
||||
@ -103,7 +141,7 @@ server
|
||||
case "types":
|
||||
case "dex-ids":
|
||||
result = unique(
|
||||
Card.raw(lang)
|
||||
Card.getAll(lang)
|
||||
.map((c) => c[endpointToField[endpoint]] as Array<string>)
|
||||
.filter((c) => c)
|
||||
.reduce((p, c) => [...p, ...c], [] as Array<string>)
|
||||
@ -111,7 +149,7 @@ server
|
||||
break
|
||||
case "variants":
|
||||
result = unique(
|
||||
Card.raw(lang)
|
||||
Card.getAll(lang)
|
||||
.map((c) => objectKeys(c.variants ?? {}) as Array<string>)
|
||||
.filter((c) => c)
|
||||
.reduce((p, c) => [...p, ...c], [] as Array<string>)
|
||||
@ -148,23 +186,23 @@ server
|
||||
let result: any | undefined
|
||||
switch (endpoint) {
|
||||
case 'cards':
|
||||
result = Card.findOne(lang, {id})?.full()
|
||||
result = Card.findOne(lang, { filters: { id }})?.full()
|
||||
if (!result) {
|
||||
result = Card.findOne(lang, {name: id})?.full()
|
||||
result = Card.findOne(lang, { filters: { name: id }})?.full()
|
||||
}
|
||||
break
|
||||
|
||||
case 'sets':
|
||||
result = Set.findOne(lang, {id})?.full()
|
||||
result = Set.findOne(lang, { filters: { id }})?.full()
|
||||
if (!result) {
|
||||
result = Set.findOne(lang, {name: id})?.full()
|
||||
result = Set.findOne(lang, {filters: { name: id }})?.full()
|
||||
}
|
||||
break
|
||||
|
||||
case 'series':
|
||||
result = Serie.findOne(lang, {id})?.full()
|
||||
result = Serie.findOne(lang, { filters: { id }})?.full()
|
||||
if (!result) {
|
||||
result = Serie.findOne(lang, {name: id})?.full()
|
||||
result = Serie.findOne(lang, { filters: { name: id }})?.full()
|
||||
}
|
||||
break
|
||||
default:
|
||||
@ -204,7 +242,7 @@ server
|
||||
switch (endpoint) {
|
||||
case 'sets':
|
||||
result = Card
|
||||
.findOne(lang, {localId: subid, set: id})?.full()
|
||||
.findOne(lang, { filters: { localId: subid, set: id }})?.full()
|
||||
break
|
||||
}
|
||||
if (!result) {
|
||||
|
@ -11,7 +11,7 @@ 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())
|
||||
const schema = buildSchema(fs.readFileSync('./public/v2/graphql.gql', 'utf-8'))
|
||||
|
||||
// Error Logging for debugging
|
||||
function graphQLErrorHandle(error: GraphQLError) {
|
||||
@ -26,19 +26,15 @@ function graphQLErrorHandle(error: GraphQLError) {
|
||||
return formatError(error)
|
||||
}
|
||||
|
||||
// Add graphql to the route
|
||||
router.get('/', graphqlHTTP({
|
||||
const graphql = graphqlHTTP({
|
||||
schema,
|
||||
rootValue: resolver,
|
||||
graphiql: true,
|
||||
customFormatErrorFn: graphQLErrorHandle
|
||||
}))
|
||||
})
|
||||
|
||||
router.post('/', graphqlHTTP({
|
||||
schema,
|
||||
rootValue: resolver,
|
||||
graphiql: true,
|
||||
customFormatErrorFn: graphQLErrorHandle
|
||||
}))
|
||||
// Add graphql to the route
|
||||
router.get('/', graphql)
|
||||
router.post('/', graphql)
|
||||
|
||||
export default router
|
||||
|
@ -1,19 +1,25 @@
|
||||
import { SupportedLanguages } from '@tcgdex/sdk'
|
||||
import { Query } from '../../interfaces'
|
||||
import { checkLanguage } from '../../util'
|
||||
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,
|
||||
const middleware = (fn: (lang: SupportedLanguages, query: Query) => any) => (
|
||||
data: Query,
|
||||
_: any,
|
||||
e: any
|
||||
) => {
|
||||
|
||||
// get the locale directive
|
||||
const langArgument = e?.fieldNodes?.[0]?.directives?.[0]?.arguments?.[0]?.value
|
||||
|
||||
// Deprecated code handling
|
||||
// @ts-expect-error count is deprectaed in the frontend
|
||||
if (data.pagination?.count) {
|
||||
// @ts-expect-error count is deprectaed in the frontend
|
||||
data.pagination.itemsPerPage = data.pagination.count
|
||||
}
|
||||
|
||||
// if there is no locale directive
|
||||
if (!langArgument) {
|
||||
return fn('en', data)
|
||||
@ -37,28 +43,27 @@ const middleware = <Q extends Record<string, any> = Record<string, any>>(fn: (la
|
||||
|
||||
export default {
|
||||
// Cards Endpoints
|
||||
cards: middleware<Options<keyof Card['card']>>((lang, query) => {
|
||||
return Card.find(lang, query.filters ?? {}, query.pagination)
|
||||
cards: middleware((lang, query) => {
|
||||
return Card.find(lang, query)
|
||||
}),
|
||||
card: middleware<{set?: string, id: string}>((lang, query) => {
|
||||
const toSearch = query.set ? 'localId' : 'id'
|
||||
return Card.findOne(lang, {[toSearch]: query.id})
|
||||
card: middleware((lang, query) => {
|
||||
return Card.findOne(lang, query)
|
||||
}),
|
||||
|
||||
// Set Endpoints
|
||||
set: middleware<{id: string}>((lang, query) => {
|
||||
return Set.findOne(lang, {id: query.id}) ?? Set.findOne(lang, {name: query.id})
|
||||
set: middleware((lang, query) => {
|
||||
return Set.findOne(lang, query)
|
||||
}),
|
||||
sets: middleware<Options<keyof Set['set']>>((lang, query) => {
|
||||
return Set.find(lang, query.filters ?? {}, query.pagination)
|
||||
sets: middleware((lang, query) => {
|
||||
return Set.find(lang, query)
|
||||
}),
|
||||
|
||||
// Serie Endpoints
|
||||
serie: middleware<{id: string}>((lang, query) => {
|
||||
return Serie.findOne(lang, {id: query.id}) ?? Serie.findOne(lang, {name: query.id})
|
||||
serie: middleware((lang, query) => {
|
||||
return Serie.findOne(lang, query)
|
||||
}),
|
||||
series: middleware<Options<keyof Serie['serie']>>((lang, query) => {
|
||||
return Serie.find(lang, query.filters ?? {}, query.pagination)
|
||||
series: middleware((lang, query) => {
|
||||
return Serie.find(lang, query)
|
||||
}),
|
||||
|
||||
};
|
||||
|
Reference in New Issue
Block a user