Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into feat/add-advanced-f…
Browse files Browse the repository at this point in the history
…iltering-capabilities

Signed-off-by: Avior <git@avior.me>
  • Loading branch information
Aviortheking committed Jul 25, 2024
2 parents 5a522ee + 31b1ae5 commit d39d03d
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 129 deletions.
Binary file modified server/bun.lockb
Binary file not shown.
55 changes: 27 additions & 28 deletions server/package.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
{
"name": "@tcgdex/server",
"private": true,
"main": "dist/index.js",
"scripts": {
"compile": "bun compiler/index.ts",
"dev": "bun --watch src/index.ts",
"validate": "tsc --noEmit --project ./tsconfig.json",
"start": "bun src/index.ts"
},
"license": "MIT",
"dependencies": {
"@dzeio/config": "^1",
"@dzeio/object-util": "^1",
"@sentry/node": "^7",
"@tcgdex/sdk": "^2",
"apicache": "^1",
"express": "^4",
"graphql": "^15",
"graphql-http": "^1.22.1",
"ruru": "^2.0.0-beta.11"
},
"devDependencies": {
"@types/apicache": "^1",
"@types/express": "^4",
"@types/node": "^20",
"glob": "^10",
"typescript": "^4"
}
"name": "@tcgdex/server",
"private": true,
"main": "dist/index.js",
"scripts": {
"compile": "bun compiler/index.ts",
"dev": "bun --watch src/index.ts",
"validate": "tsc --noEmit --project ./tsconfig.json",
"start": "bun src/index.ts"
},
"license": "MIT",
"dependencies": {
"@dzeio/config": "^1",
"@dzeio/object-util": "^1",
"@tcgdex/sdk": "^2",
"apicache": "^1",
"express": "^4",
"graphql": "^15",
"graphql-http": "^1.22.1",
"ruru": "^2.0.0-beta.14"
},
"devDependencies": {
"@types/apicache": "^1",
"@types/express": "^4",
"@types/node": "^20",
"glob": "^10",
"typescript": "^4"
}
}
Binary file modified server/public/favicon.ico
Binary file not shown.
5 changes: 5 additions & 0 deletions server/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
User-agent: *
Disallow: /

# Please note that this is for Crawlers only
# You can logically use robots to use the API
3 changes: 3 additions & 0 deletions server/public/sitemap.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
</urlset>
25 changes: 15 additions & 10 deletions server/src/V2/endpoints/jsonEndpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { objectKeys } from '@dzeio/object-util'
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 type { Query } from '../../libs/QueryEngine/filter'
import { recordToQuery } from '../../libs/QueryEngine/parsers'
import { betterSorter, checkLanguage, sendError, unique } from '../../util'
import { betterSorter, checkLanguage, unique } from '../../util'
import Card from '../Components/Card'
import Serie from '../Components/Serie'
import TCGSet from '../Components/Set'
Expand Down Expand Up @@ -78,7 +79,7 @@ server
const { lang, what } = req.params

if (!checkLanguage(lang)) {
sendError('LanguageNotFoundError', res, lang)
sendError(Errors.LANGUAGE_INVALID, res, { lang })
return
}

Expand All @@ -97,7 +98,7 @@ server
data = Serie.find(lang, query)
break
default:
sendError('EndpointNotFoundError', res, what)
sendError(Errors.NOT_FOUND, res, { details: `You can only run random requests on "card", "set" or "serie" while you did on "${what}"` })
return
}
const item = Math.min(data.length - 1, Math.max(0, Math.round(Math.random() * data.length)))
Expand All @@ -120,7 +121,7 @@ server
}

if (!checkLanguage(lang)) {
sendError('LanguageNotFoundError', res, lang)
sendError(Errors.LANGUAGE_INVALID, res, { lang })
return
}

Expand Down Expand Up @@ -177,12 +178,12 @@ server
).sort()
break
default:
sendError('EndpointNotFoundError', res, endpoint)
sendError(Errors.NOT_FOUND, res, { endpoint })
return
}

if (!result) {
sendError('NotFoundError', res)
sendError(Errors.NOT_FOUND, res)
}
res.json(result)
})
Expand All @@ -201,7 +202,7 @@ server
id = id.toLowerCase()

if (!checkLanguage(lang)) {
return sendError('LanguageNotFoundError', res, lang)
return sendError(Errors.LANGUAGE_INVALID, res, { lang })
}

let result: unknown
Expand All @@ -227,14 +228,18 @@ server
}
break
default:
if (!endpointToField[endpoint]) {
break
}
result = {
name: id,
cards: Card.find(lang, {[endpointToField[endpoint]]: id})
.map((c) => c.resume())
}
}
if (!result) {
return res.status(404).send({error: "Endpoint or id not found"})
sendError(Errors.NOT_FOUND, res)
return
}
return res.send(result)

Expand All @@ -255,7 +260,7 @@ server
subid = subid.toLowerCase()

if (!checkLanguage(lang)) {
return sendError('LanguageNotFoundError', res, lang)
return sendError(Errors.LANGUAGE_INVALID, res, { lang })
}

let result: unknown
Expand All @@ -267,7 +272,7 @@ server
break
}
if (!result) {
return sendError('NotFoundError', res)
return sendError(Errors.NOT_FOUND, res)
}
return res.send(result)
})
Expand Down
30 changes: 11 additions & 19 deletions server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Sentry from '@sentry/node'
import express, { NextFunction } from 'express'
import express, { type Response } from 'express'
import jsonEndpoints from './V2/endpoints/jsonEndpoints'
import graphql from './V2/graphql'
import { Errors, sendError } from './libs/Errors'
import status from './status'

// Current API version
Expand All @@ -10,20 +10,11 @@ const VERSION = 2
// Init Express server
const server = express()

// allow to catch servers errors
const sentryDSN = process.env.SENTRY_DSN

if (sentryDSN) {
Sentry.init({ dsn: sentryDSN})

server.use(Sentry.Handlers.requestHandler())
}

// Route logging / Error logging for debugging
server.use((req, res, next) => {
const now = new Date()
// 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)}`
const prefix = `\x1b[2m${now.toISOString()}\x1b[22m ${req.headers['user-agent']?.slice(0, 32).padEnd(32)} ${req.method.toUpperCase().padEnd(7)}`

const url = new URL(req.url, `http://${req.headers.host}`)
const fullURL = url.toString()
Expand Down Expand Up @@ -82,12 +73,13 @@ server.use(`/v${VERSION}`, jsonEndpoints)
// Status page
server.use('/status', status)

if (sentryDSN) {
server.use(Sentry.Handlers.errorHandler())
}
// handle 404 errors
server.use((_, res) => {
sendError(Errors.NOT_FOUND, res)
})

// error logging to the backend
server.use((err: Error, _1: any, _2: any, next: NextFunction) => {
// General error handler
server.use((err: Error, _1: unknown, res: Response, _2: unknown) => {
// add a full line dash to not miss it
const columns = (process?.stdout?.columns ?? 32) - 7
const dashes = ''.padEnd(columns / 2, '-')
Expand All @@ -97,9 +89,9 @@ server.use((err: Error, _1: any, _2: any, next: NextFunction) => {
console.error(err)
console.error(`\x1b[91m${dashes} ERROR ${dashes}\x1b[0m`)

next(err)
sendError(Errors.GENERAL, res, { err })
})

// Start server
server.listen(3000)
console.log(`🚀 Server ready at localhost:3000`);
console.log('🚀 Server ready at localhost:3000');
46 changes: 46 additions & 0 deletions server/src/libs/Errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { Response } from 'express'
import { languages } from '../util'
import type RFC7807 from './RFCs/RFC7807'

export enum Errors {
LANGUAGE_INVALID = 'language-invalid',
NOT_FOUND = 'not-found',

GENERAL = 'general'
}

const titles: Record<Errors, string> = {
[Errors.LANGUAGE_INVALID]: 'The chosen language is not available in the database',
[Errors.NOT_FOUND]: 'The resource you are trying to reach does not exists',

[Errors.GENERAL]: 'An unknown error occured, please contact a developper with this message'
}

const status: Record<Errors, number> = {
[Errors.LANGUAGE_INVALID]: 404,
[Errors.NOT_FOUND]: 404,

[Errors.GENERAL]: 500
}

const details: Partial<Record<Errors, (meta?: Record<string, unknown>) => string>> = {
[Errors.LANGUAGE_INVALID]: (meta) => `You must use one of the following languages (${languages.join(', ')}) while you used "${meta?.lang}"`,
}

export function sendError(error: Errors, res: Response, metadata?: Record<string, unknown>) {
const json: RFC7807 & Record<string, unknown> = {
type: `https://tcgdex.dev/errors/${error}`,
title: titles[error],
status: status[error],
endpoint: res.req.url,
method: res.req.method,
...metadata
}

const dt = details[error]
if (dt) {
json.details = dt(metadata)
}

res.status(json.status ?? 500).json(json).end()
}
52 changes: 52 additions & 0 deletions server/src/libs/RFCs/RFC7807.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Add headers:
* Content-Type: application/problem+json
*
* following https://www.rfc-editor.org/rfc/rfc7807.html
*/
export default interface RFC7807 {
/**
* A URI reference [RFC3986] that identifies the
* problem type.
*
* This specification encourages that, when
* dereferenced, it provide human-readable documentation for the
* problem type (e.g., using HTML [W3C.REC-html5-20141028]).
*
* When
* this member is not present, its value is assumed to be
* "about:blank"
*/
type?: string

/**
* A short, human-readable summary of the problem
* type.
*
* It SHOULD NOT change from occurrence to occurrence of the
* problem, except for purposes of localization (e.g., using
* proactive content negotiation; see [RFC7231], Section 3.4).
*/
title?: string

/**
* The HTTP status code ([RFC7231], Section 6)
* generated by the origin server for this occurrence of the problem.
*/
status?: number

/**
* A human-readable explanation specific to this
* occurrence of the problem.
*/
details?: string

/**
* A URI reference that identifies the specific
* occurrence of the problem.
*
* It may or may not yield further
* information if dereferenced.
*/
instance?: string
}
Loading

0 comments on commit d39d03d

Please sign in to comment.