Add eslint (#12)

* Done !

Signed-off-by: Florian BOUILLON <florian.bouillon@delta-wings.net>

* ACT doesn't wanna work so I push

Signed-off-by: Florian BOUILLON <florian.bouillon@delta-wings.net>

* Continued work on ESLint

Signed-off-by: Florian BOUILLON <florian.bouillon@delta-wings.net>

* Two files remaining

Signed-off-by: Avior <github@avior.me>

* Fixed set cards not found when using ids

Signed-off-by: Avior <florian.bouillon@delta-wings.net>
This commit is contained in:
Florian Bouillon 2021-06-29 20:41:37 +00:00 committed by GitHub
parent a4d9f8f5b2
commit dd0a8731ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1626 additions and 315 deletions

3
.eslintignore Normal file
View File

@ -0,0 +1,3 @@
db/
dist/
*.js

322
.eslintrc.js Normal file
View File

@ -0,0 +1,322 @@
/**
* ESLint custom configuration v1.0.0
* packages needed:
* eslint
* for Typescript
* @typescript-eslint/parser
* @typescript-eslint/eslint-plugin
*/
module.exports = {
env: {
browser: true,
es6: true,
node: true
},
extends: [
"eslint:all",
"plugin:@typescript-eslint/recommended",
],
globals: {
Atomics: "readonly",
SharedArrayBuffer: "readonly"
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: "tsconfig.json"
},
plugins: [
"@typescript-eslint"
],
root: true,
rules: {
// Tab indent and force switch to have one too
indent: [
"error",
"tab",
{SwitchCase: 1}
],
// \n linebreak
"linebreak-style": [
"error",
"unix"
],
// Disable eslint default quote because Typescript replace it
quotes: "off",
"@typescript-eslint/quotes": [
"error",
"single",
{ avoidEscape: true }
],
// Simply diallow using ';' unless mandatory
semi: "off",
"@typescript-eslint/semi": [
"error",
"never",
{ "beforeStatementContinuationChars": "always"}
],
// Disallow things that do nothing in the end
"no-unused-expressions": "off",
"@typescript-eslint/no-unused-expressions": [
"error",
{ "enforceForJSX": true,}
],
// force overloads to be next to one another
"@typescript-eslint/adjacent-overload-signatures": "error",
// Force to use `Array<thing>
"@typescript-eslint/array-type": [
"error",
{ default: 'generic' }
],
// Warn when no return type is specified
"@typescript-eslint/explicit-module-boundary-types": "warn",
// disallow certain types not safe
"@typescript-eslint/ban-types": [
"error",
{
"types": {
"{}": false
}
}
],
"@typescript-eslint/consistent-type-assertions": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/explicit-member-accessibility": [
"error",
{
accessibility: "explicit"
}
],
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/member-delimiter-style": [
"error",
{
multiline: {
delimiter: "none",
requireLast: true
},
singleline: {
delimiter: "comma",
requireLast: false
}
}
],
"@typescript-eslint/member-ordering": "error",
"@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-parameter-properties": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/triple-slash-reference": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unified-signatures": "error",
"arrow-body-style": "error",
"arrow-parens": [
"error",
"always"
],
camelcase: "error",
complexity: "off",
"constructor-super": "error",
curly: "error",
"dot-notation": "error",
"eol-last": "error",
eqeqeq: [
"error",
"smart"
],
"guard-for-in": "warn",
"id-blacklist": [
"error",
"any",
"Number",
"number",
"String",
"string",
"Boolean",
"boolean",
"Undefined"
],
"id-match": "error",
"max-classes-per-file": [
"error",
1
],
"max-len": [
"warn",
{
code: 200
}
],
"@typescript-eslint/no-inferrable-types": "off",
"new-parens": "error",
"no-bitwise": "error",
"no-caller": "error",
"no-cond-assign": "error",
"no-debugger": "error",
"no-empty": "error",
"no-eval": "error",
"no-fallthrough": "off",
"no-invalid-this": "off",
"no-multiple-empty-lines": "error",
"no-new-wrappers": "error",
"no-shadow": [
"error",
{
hoist: "all"
}
],
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef-init": "error",
"no-underscore-dangle": "error",
"no-unsafe-finally": "error",
"no-unused-labels": "error",
"no-unused-vars": "off",
"no-var": "error",
"object-shorthand": "error",
"one-var": [
"error",
"never"
],
"prefer-const": "error",
"quote-props": [
"error",
"consistent-as-needed"
],
"radix": "error",
"space-before-function-paren": "off",
"@typescript-eslint/space-before-function-paren": ["error", {
asyncArrow: "always",
anonymous: "never",
named: "never"
}],
"spaced-comment": "error",
"use-isnan": "error",
"valid-typeof": "off",
// some tests from eslint:all
"no-tabs": "off",
"padded-blocks": [
"error",
{
"blocks": "never",
"classes": "always",
"switches": "never"
}
],
"sort-imports": "off",
"no-console": "off",
"function-call-argument-newline": [
"error",
"consistent"
],
"dot-location": [
"error",
"property"
],
"object-curly-spacing": [
"error",
"always"
],
"array-element-newline": [
"error",
"consistent"
],
"function-paren-newline": [
"error",
"consistent"
],
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "error",
"capitalized-comments": "off",
"multiline-comment-style": "off",
"no-extra-parens": "off",
"@typescript-eslint/no-extra-parens": [
"error",
"all",
{ "ignoreJSX": "multi-line" }
],
"func-style": [
"error",
"declaration",
{ "allowArrowFunctions": true }
],
"no-ternary": "off",
"multiline-ternary": "off",
"no-magic-numbers": "off",
"max-lines-per-function": [
"warn",
{
"skipBlankLines": true,
"skipComments": true
}
],
"prefer-promise-reject-errors": "warn",
"object-property-newline": [
"error",
{ "allowAllPropertiesOnSameLine": true }
],
"no-await-in-loop": "warn",
"no-undefined": "off",
"id-length": "warn",
"class-methods-use-this": "off",
"array-bracket-newline": [
"error",
"consistent"
],
"no-confusing-arrow": "off",
"no-nested-ternary": "off",
"no-mixed-operators": "off",
"max-statements": [
"warn",
15
],
"semi-style": [
"error",
"first"
],
"no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": "error",
"lines-between-class-members": "off",
"@typescript-eslint/lines-between-class-members": "error",
"max-lines": [
"warn",
{
"max": 500,
"skipBlankLines": true,
"skipComments": true
}
],
"no-plusplus": "off",
"id-length": [
"warn",
{ "exceptions": ["_"] }
],
"default-param-last": "off",
// "@typescript-eslint/default-param-last": "error",
// Temporary OFF
"@typescript-eslint/default-param-last": "off",
"no-continue": "off",
"require-atomic-updates": "off",
"require-await": "off",
"prefer-destructuring": "off",
"max-params": ["warn", 5]
}
};

View File

@ -16,6 +16,27 @@ on:
pull_request: pull_request:
jobs: jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
persist-credentials: false
- uses: actions/checkout@v2
with:
repository: tcgdex/cards-database
path: ./db
persist-credentials: false
- name: Setup NodeJS
uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Lint
run: yarn && yarn lint
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -1,4 +1,4 @@
import { ConnectConfig, Client, SFTPWrapper } from "ssh2"; import { ConnectConfig, Client, SFTPWrapper } from 'ssh2'
import { Stats, InputAttributes } from 'ssh2-streams' import { Stats, InputAttributes } from 'ssh2-streams'
import { promises as fs } from 'fs' import { promises as fs } from 'fs'
import { posix as pathJS } from 'path' import { posix as pathJS } from 'path'
@ -7,14 +7,25 @@ import Logger from '@dzeio/logger'
const logger = new Logger('SFTPPromise') const logger = new Logger('SFTPPromise')
export default class SFTPPromise { export default class SFTPPromise {
private sftp?: SFTPWrapper
private conn: Client = new Client()
public debug = false public debug = false
public tmpFolder?: string public tmpFolder?: string
private sftp?: SFTPWrapper
private conn: Client = new Client()
private queue = new Queue(50, 10)
private filesToUpload = 0
private filesUploaded = 0
private lastTimeDiff: Array<number> = Array.from(new Array(900), () => 0)
public constructor(public config: ConnectConfig) {} public constructor(public config: ConnectConfig) {}
public connect() { public connect(): Promise<void> {
return new Promise<void>((res, rej) => { return new Promise<void>((res, rej) => {
this.conn.on('ready', () => { this.conn.on('ready', () => {
this.conn.sftp((err, sftpLocal) => { this.conn.sftp((err, sftpLocal) => {
@ -25,7 +36,7 @@ export default class SFTPPromise {
}) })
} }
public async makeTemporaryFolder() { public async makeTemporaryFolder(): Promise<void> {
this.l('Making temporary Folder') this.l('Making temporary Folder')
this.tmpFolder = await fs.mkdtemp('tcgdex-generator') this.tmpFolder = await fs.mkdtemp('tcgdex-generator')
} }
@ -51,25 +62,25 @@ export default class SFTPPromise {
} }
} }
public async mkdir(path: string, recursive = false, attributes?: InputAttributes, ) { public async mkdir(path: string, recursive = false, attributes?: InputAttributes): Promise<void> {
this.l('Creating remote folder', path) this.l('Creating remote folder', path)
if (recursive) { if (recursive) {
this.l('Making temporary Folder') this.l('Making temporary Folder')
const folders = path.split('/') const folders = path.split('/')
let current = '' let current = ''
for (const item of folders) { await Promise.all(folders.map(async (item) => {
current += '/' + item current += `/${item}`
if (!(await this.exists(current))) { if (!await this.exists(current)) {
await this.mkdir(current) await this.mkdir(current)
} }
} }))
return return
} }
if (await this.exists(path)) { if (await this.exists(path)) {
return return
} }
const sftp = this.getSFTP() const sftp = this.getSFTP()
return new Promise<void>((res, rej) => { await new Promise<void>((res, rej) => {
const result = (err?: any) => { const result = (err?: any) => {
if (err) { if (err) {
rej(err) rej(err)
@ -81,66 +92,68 @@ export default class SFTPPromise {
} else { } else {
sftp.mkdir(path, result) sftp.mkdir(path, result)
} }
}) })
} }
public async upload(localFile: Buffer|string, path: string) { public async upload(localFile: Buffer|string, path: string): Promise<void> {
const sftp = this.getSFTP() const sftp = this.getSFTP()
if (typeof localFile !== 'string') { let file = localFile
if (typeof file !== 'string') {
if (!this.tmpFolder) { if (!this.tmpFolder) {
await this.makeTemporaryFolder() await this.makeTemporaryFolder()
} }
const tmpFile = pathJS.join(this.tmpFolder as string, Math.random().toString().replace('.', '')) const tmpFile = pathJS.join(
// this.l('Writing to temporary Folder') this.tmpFolder as string,
await fs.writeFile(tmpFile, localFile) Math.random()
localFile = tmpFile .toString()
.replace('.', '')
)
await fs.writeFile(tmpFile, file)
// IDK there is no race condition
// eslint-disable-next-line require-atomic-updates
file = tmpFile
} }
const remoteFolder = pathJS.dirname(path).replace(/\\/g, '/') const remoteFolder = pathJS.dirname(path).replace(/\\/gu, '/')
if (!(await this.exists(remoteFolder))) { if (!await this.exists(remoteFolder)) {
await this.mkdir(remoteFolder, true) await this.mkdir(remoteFolder, true)
} }
return new Promise<void>((result, rej) => { await new Promise<void>((result, rej) => {
this.l('Sending file', path) this.l('Sending file', path)
sftp.fastPut(localFile as string, path, { sftp.fastPut(file as string, path, {
concurrency: 1, concurrency: 1,
step: (uploaded, u, total) => { step: (uploaded, _, total) => {
this.l(path.substr(path.lastIndexOf('/')), Math.round(uploaded*100/total),'%', '/', 100, '%') this.l(path.substr(path.lastIndexOf('/')), Math.round(uploaded * 100 / total), '%', '/', 100, '%')
} }
}, (err) => { }, (err) => {
if (err) { if (err) {
this.l('Error fastPutting file', localFile, 'to', path) this.l('Error fastPutting file', file, 'to', path)
rej(err) rej(err)
} }
this.l('Done') this.l('Done')
result() result()
return
}) })
}) })
} }
private queue = new Queue(50, 10)
private filesToUpload = 0
private filesUploaded = 0
public async listDir(path: string, exclude?: RegExp): Promise<Array<string>> { public async listDir(path: string, exclude?: RegExp): Promise<Array<string>> {
const files = await fs.readdir(path) const files = await fs.readdir(path)
logger.log('Reading', path) logger.log('Reading', path)
let res: Array<string> = [] const res: Array<string> = []
for (const file of files) { await Promise.all(files.map(async (file) => {
if (exclude?.test(file)) {continue} if (exclude?.test(file)) {
return
}
const filePath = `${path}/${file}` const filePath = `${path}/${file}`
const stat = await fs.stat(filePath) const stat = await fs.stat(filePath)
if (stat.isDirectory()) { if (stat.isDirectory()) {
res = res.concat(await this.listDir(filePath)) res.push(...await this.listDir(filePath))
} else { } else {
res.push(filePath) res.push(filePath)
} }
} }))
return res return res
} }
private lastTimeDiff: Array<number> = Array.from(new Array(900), () => 0)
public async uploadDir(localPath: string, remotePath: string, exclude?: RegExp, root = true) { public async uploadDir(localPath: string, remotePath: string, exclude?: RegExp, root = true) {
if (root) { if (root) {
this.filesToUpload = 0 this.filesToUpload = 0
@ -154,20 +167,24 @@ export default class SFTPPromise {
this.queue.start() this.queue.start()
for (const file of files) { for (const file of files) {
// console.log('t1') // console.log('t1')
if (exclude?.test(file)) {continue} if (exclude?.test(file)) {
continue
}
// console.log('t1') // console.log('t1')
const filePath = file const filePath = file
const remoteFilePath = `${remotePath}/${file.replace(localPath, '')}` const remoteFilePath = `${remotePath}/${file.replace(localPath, '')}`
// console.log('t1') // console.log('t1')
const now = new Date().getTime() const now = new Date().getTime()
await this.queue.add( await this.queue.add(
this.upload(filePath, remoteFilePath).then(() => { this.upload(filePath, remoteFilePath)
this.filesUploaded++ .then(() => {
this.lastTimeDiff.push(new Date().getTime() - now) this.filesUploaded++
this.lastTimeDiff.shift() this.lastTimeDiff.push(new Date().getTime() - now)
const time = ((this.filesToUpload-this.filesUploaded)*(this.lastTimeDiff.reduce((p, c) => p + c, 0)/this.lastTimeDiff.length)/10000) this.lastTimeDiff.shift()
console.log(`Files uploaded ${(this.filesUploaded * 100 / this.filesToUpload).toFixed(0)}% ${time > 60 ? `${(time/60).toFixed(0)}m` : `${time.toFixed(0)}s`} ${this.filesUploaded}/${this.filesToUpload}`) const time = (this.filesToUpload - this.filesUploaded) * (this.lastTimeDiff.reduce((p, c) => p + c, 0) / this.lastTimeDiff.length) / 10000
}).catch((err) => logger.log(err, 'Error uploading', filePath, 'to', remoteFilePath)) console.log(`Files uploaded ${(this.filesUploaded * 100 / this.filesToUpload).toFixed(0)}% ${time > 60 ? `${(time/60).toFixed(0)}m` : `${time.toFixed(0)}s`} ${this.filesUploaded}/${this.filesToUpload}`)
})
.catch((err) => logger.log(err, 'Error uploading', filePath, 'to', remoteFilePath))
) )
} }
if (root) { if (root) {
@ -188,4 +205,5 @@ export default class SFTPPromise {
logger.log(...messages) logger.log(...messages)
} }
} }
} }

View File

@ -3,24 +3,26 @@ import { Card, Languages } from '../db/interfaces'
import { Endpoint } from '../interfaces' import { Endpoint } from '../interfaces'
import { cardToCardSimple, cardToCardSingle, getCards } from '../utils/cardUtil' import { cardToCardSimple, cardToCardSingle, getCards } from '../utils/cardUtil'
export default class implements Endpoint<CardList, CardSingle, {}, Array<[string, Card]>> { export default class implements Endpoint<CardList, CardSingle, Record<string, unknown>, Array<[string, Card]>> {
public constructor( public constructor(
private lang: keyof Languages private lang: keyof Languages
) {} ) {}
public async index(common: Array<[string, Card]>) { public async index(common: Array<[string, Card]>): Promise<CardList> {
return Promise.all(common.map((c) => cardToCardSimple(c[0], c[1], this.lang))) return Promise.all(common.map((c) => cardToCardSimple(c[0], c[1], this.lang)))
} }
public async item(common: Array<[string, Card]>) { public async item(common: Array<[string, Card]>): Promise<Record<string, CardSingle>> {
const items: Record<string, CardSingle> = {} const items: Record<string, CardSingle> = {}
for (const card of common) { for await (const card of common) {
items[`${card[1].set.id}-${card[0]}`] = await cardToCardSingle(card[0], card[1], this.lang) items[`${card[1].set.id}-${card[0]}`] = await cardToCardSingle(card[0], card[1], this.lang)
} }
return items return items
} }
public async common() { public async common(): Promise<Array<[string, Card]>> {
return getCards(this.lang) return getCards(this.lang)
} }
} }

View File

@ -4,32 +4,35 @@ import { Card, Languages } from '../db/interfaces'
import { Endpoint } from '../interfaces' import { Endpoint } from '../interfaces'
import { cardToCardSimple, getCards } from '../utils/cardUtil' import { cardToCardSimple, getCards } from '../utils/cardUtil'
export default class implements Endpoint<StringEndpointList, StringEndpoint, {}, Record<string, Array<[string, Card]>>> { export default class implements Endpoint<StringEndpointList, StringEndpoint, Record<string, unknown>, Record<string, Array<[string, Card]>>> {
public constructor( public constructor(
private lang: keyof Languages private lang: keyof Languages
) {} ) {}
public async index(common: Record<string, Array<[string, Card]>>) { public async index(common: Record<string, Array<[string, Card]>>): Promise<StringEndpointList> {
return Object.keys(common) return Object.keys(common)
} }
public async item(common: Record<string, Array<[string, Card]>>) { public async item(common: Record<string, Array<[string, Card]>>): Promise<Record<string, StringEndpoint>> {
const items: Record<string, StringEndpoint> = {} const items: Record<string, StringEndpoint> = {}
for (const key of Object.keys(common)) { for await (const key of Object.keys(common)) {
const val = common[key] const val = common[key]
const it = { const it = {
name: key, cards: await Promise.all(val.map(([id, card]) => cardToCardSimple(id, card, this.lang))),
cards: await Promise.all(val.map(([id, card]) => cardToCardSimple(id, card, this.lang))) name: key
} }
items[key] = it items[key] = it
} }
return items return items
} }
public async common() { public async common(): Promise<Record<string, Array<[string, Card]>>> {
return (await getCards(this.lang)).reduce((p, c) => { return (await getCards(this.lang)).reduce((p, c) => {
const category = translate('category', c[1].category, this.lang) const category = translate('category', c[1].category, this.lang)
if (!category) return p if (!category) {
return p
}
if (!p[category]) { if (!p[category]) {
p[category] = [] p[category] = []
} }
@ -37,4 +40,5 @@ export default class implements Endpoint<StringEndpointList, StringEndpoint, {},
return p return p
}, {} as Record<string, Array<[string, Card]>>) }, {} as Record<string, Array<[string, Card]>>)
} }
} }

View File

@ -3,31 +3,34 @@ import { Card, Languages } from '../db/interfaces'
import { Endpoint } from '../interfaces' import { Endpoint } from '../interfaces'
import { cardToCardSimple, getCards } from '../utils/cardUtil' import { cardToCardSimple, getCards } from '../utils/cardUtil'
export default class implements Endpoint<StringEndpointList, StringEndpoint, {}, Record<string, Array<[string, Card]>>> { export default class implements Endpoint<StringEndpointList, StringEndpoint, Record<string, unknown>, Record<string, Array<[string, Card]>>> {
public constructor( public constructor(
private lang: keyof Languages private lang: keyof Languages
) {} ) {}
public async index(common: Record<string, Array<[string, Card]>>) { public async index(common: Record<string, Array<[string, Card]>>): Promise<StringEndpointList> {
return Object.keys(common) return Object.keys(common)
} }
public async item(common: Record<string, Array<[string, Card]>>) { public async item(common: Record<string, Array<[string, Card]>>): Promise<Record<string, StringEndpoint>> {
const items: Record<string, StringEndpoint> = {} const items: Record<string, StringEndpoint> = {}
for (const key of Object.keys(common)) { for (const key of Object.keys(common)) {
const val = common[key] const val = common[key]
items[key] = { items[key] = {
name: key, cards: await Promise.all(val.map(([id, card]) => cardToCardSimple(id, card, this.lang))),
cards: await Promise.all(val.map(([id, card]) => cardToCardSimple(id, card, this.lang))) name: key
} }
} }
return items return items
} }
public async common() { public async common(): Promise<Record<string, Array<[string, Card]>>> {
return (await getCards(this.lang)).reduce((p, c) => { return (await getCards(this.lang)).reduce((p, c) => {
const hp = c[1].hp const { hp } = c[1]
if (!hp) return p if (!hp) {
return p
}
if (!p[hp]) { if (!p[hp]) {
p[hp] = [] p[hp] = []
} }
@ -35,4 +38,5 @@ export default class implements Endpoint<StringEndpointList, StringEndpoint, {},
return p return p
}, {} as Record<string, Array<[string, Card]>>) }, {} as Record<string, Array<[string, Card]>>)
} }
} }

View File

@ -3,31 +3,34 @@ import { Card, Languages } from '../db/interfaces'
import { Endpoint } from '../interfaces' import { Endpoint } from '../interfaces'
import { cardToCardSimple, getCards } from '../utils/cardUtil' import { cardToCardSimple, getCards } from '../utils/cardUtil'
export default class implements Endpoint<StringEndpointList, StringEndpoint, {}, Record<string, Array<[string, Card]>>> { export default class implements Endpoint<StringEndpointList, StringEndpoint, Record<string, unknown>, Record<string, Array<[string, Card]>>> {
public constructor( public constructor(
private lang: keyof Languages private lang: keyof Languages
) {} ) {}
public async index(common: Record<string, Array<[string, Card]>>) { public async index(common: Record<string, Array<[string, Card]>>): Promise<StringEndpointList> {
return Object.keys(common) return Object.keys(common)
} }
public async item(common: Record<string, Array<[string, Card]>>) { public async item(common: Record<string, Array<[string, Card]>>): Promise<Record<string, StringEndpoint>> {
const items: Record<string, StringEndpoint> = {} const items: Record<string, StringEndpoint> = {}
for (const key of Object.keys(common)) { for await (const key of Object.keys(common)) {
const val = common[key] const val = common[key]
items[key] = { items[key] = {
name: key, cards: await Promise.all(val.map(([id, card]) => cardToCardSimple(id, card, this.lang))),
cards: await Promise.all(val.map(([id, card]) => cardToCardSimple(id, card, this.lang))) name: key
} }
} }
return items return items
} }
public async common() { public async common(): Promise<Record<string, Array<[string, Card]>>> {
return (await getCards(this.lang)).reduce((p, c) => { return (await getCards(this.lang)).reduce((p, c) => {
const illustrator = c[1].illustrator const { illustrator } = c[1]
if (!illustrator) return p if (!illustrator) {
return p
}
if (!p[illustrator]) { if (!p[illustrator]) {
p[illustrator] = [] p[illustrator] = []
} }
@ -35,4 +38,5 @@ export default class implements Endpoint<StringEndpointList, StringEndpoint, {},
return p return p
}, {} as Record<string, Array<[string, Card]>>) }, {} as Record<string, Array<[string, Card]>>)
} }
} }

View File

@ -4,31 +4,34 @@ import { Card, Languages } from '../db/interfaces'
import { Endpoint } from '../interfaces' import { Endpoint } from '../interfaces'
import { cardToCardSimple, getCards } from '../utils/cardUtil' import { cardToCardSimple, getCards } from '../utils/cardUtil'
export default class implements Endpoint<StringEndpointList, StringEndpoint, {}, Record<string, Array<[string, Card]>>> { export default class implements Endpoint<StringEndpointList, StringEndpoint, Record<string, unknown>, Record<string, Array<[string, Card]>>> {
public constructor( public constructor(
private lang: keyof Languages private lang: keyof Languages
) {} ) {}
public async index(common: Record<string, Array<[string, Card]>>) { public async index(common: Record<string, Array<[string, Card]>>): Promise<StringEndpointList> {
return Object.keys(common) return Object.keys(common)
} }
public async item(common: Record<string, Array<[string, Card]>>) { public async item(common: Record<string, Array<[string, Card]>>): Promise<Record<string, StringEndpoint>> {
const items: Record<string, StringEndpoint> = {} const items: Record<string, StringEndpoint> = {}
for (const key of Object.keys(common)) { for await (const key of Object.keys(common)) {
const val = common[key] const val = common[key]
items[key] = { items[key] = {
name: key, cards: await Promise.all(val.map(([id, card]) => cardToCardSimple(id, card, this.lang))),
cards: await Promise.all(val.map(([id, card]) => cardToCardSimple(id, card, this.lang))) name: key
} }
} }
return items return items
} }
public async common() { public async common(): Promise<Record<string, Array<[string, Card]>>> {
return (await getCards(this.lang)).reduce((p, c) => { return (await getCards(this.lang)).reduce((p, c) => {
const rarity = translate('rarity', c[1].rarity, this.lang) const rarity = translate('rarity', c[1].rarity, this.lang)
if (!rarity) return p if (!rarity) {
return p
}
if (!p[rarity]) { if (!p[rarity]) {
p[rarity] = [] p[rarity] = []
} }
@ -36,4 +39,5 @@ export default class implements Endpoint<StringEndpointList, StringEndpoint, {},
return p return p
}, {} as Record<string, Array<[string, Card]>>) }, {} as Record<string, Array<[string, Card]>>)
} }
} }

View File

@ -3,31 +3,34 @@ import { Card, Languages } from '../db/interfaces'
import { Endpoint } from '../interfaces' import { Endpoint } from '../interfaces'
import { cardToCardSimple, getCards } from '../utils/cardUtil' import { cardToCardSimple, getCards } from '../utils/cardUtil'
export default class implements Endpoint<StringEndpointList, StringEndpoint, {}, Record<string, Array<[string, Card]>>> { export default class implements Endpoint<StringEndpointList, StringEndpoint, Record<string, unknown>, Record<string, Array<[string, Card]>>> {
public constructor( public constructor(
private lang: keyof Languages private lang: keyof Languages
) {} ) {}
public async index(common: Record<string, Array<[string, Card]>>) { public async index(common: Record<string, Array<[string, Card]>>): Promise<StringEndpointList> {
return Object.keys(common) return Object.keys(common)
} }
public async item(common: Record<string, Array<[string, Card]>>) { public async item(common: Record<string, Array<[string, Card]>>): Promise<Record<string, StringEndpoint>> {
const items: Record<string, StringEndpoint> = {} const items: Record<string, StringEndpoint> = {}
for (const key of Object.keys(common)) { for await (const key of Object.keys(common)) {
const val = common[key] const val = common[key]
items[key] = { items[key] = {
name: key, cards: await Promise.all(val.map(([id, card]) => cardToCardSimple(id, card, this.lang))),
cards: await Promise.all(val.map(([id, card]) => cardToCardSimple(id, card, this.lang))) name: key
} }
} }
return items return items
} }
public async common() { public async common(): Promise<Record<string, Array<[string, Card]>>> {
return (await getCards(this.lang)).reduce((p, c) => { return (await getCards(this.lang)).reduce((p, c) => {
const retreat = c[1].retreat const { retreat } = c[1]
if (!retreat) return p if (!retreat) {
return p
}
if (!p[retreat]) { if (!p[retreat]) {
p[retreat] = [] p[retreat] = []
} }
@ -35,4 +38,5 @@ export default class implements Endpoint<StringEndpointList, StringEndpoint, {},
return p return p
}, {} as Record<number, Array<[string, Card]>>) }, {} as Record<number, Array<[string, Card]>>)
} }
} }

View File

@ -1,21 +1,21 @@
import { Serie as SerieSingle, SerieList } from '@tcgdex/sdk/interfaces' import { Serie as SerieSingle, SerieList, SerieResume } from '@tcgdex/sdk/interfaces'
import { Languages, Serie } from '../db/interfaces' import { Languages, Serie } from '../db/interfaces'
import { Endpoint } from '../interfaces' import { Endpoint } from '../interfaces'
import { getSeries, serieToSerieSimple, serieToSerieSingle } from '../utils/serieUtil' import { getSeries, serieToSerieSimple, serieToSerieSingle } from '../utils/serieUtil'
export default class implements Endpoint<SerieList, SerieSingle, {}, Array<Serie>> { export default class implements Endpoint<SerieList, SerieSingle, Record<string, any>, Array<Serie>> {
public constructor( public constructor(
private lang: keyof Languages private lang: keyof Languages
) {} ) {}
public async index(common: Array<Serie>) { public async index(common: Array<Serie>): Promise<Array<SerieResume>> {
return Promise.all(common.map((s) => serieToSerieSimple(s, this.lang))) return Promise.all(common.map((s) => serieToSerieSimple(s, this.lang)))
} }
public async item(common: Array<Serie>) { public async item(common: Array<Serie>): Promise<Record<string, SerieSingle>> {
const items: Record<string, SerieSingle> = {} const items: Record<string, SerieSingle> = {}
for (let key = 0; key < common.length; key++) { for await (const val of common) {
const val = common[key];
const gen = await serieToSerieSingle(val, this.lang) const gen = await serieToSerieSingle(val, this.lang)
const name = val.name[this.lang] as string const name = val.name[this.lang] as string
items[name] = gen items[name] = gen
@ -24,7 +24,8 @@ export default class implements Endpoint<SerieList, SerieSingle, {}, Array<Serie
return items return items
} }
public async common() { public async common(): Promise<Array<Serie>> {
return getSeries(this.lang) return getSeries(this.lang)
} }
} }

View File

@ -1,15 +1,16 @@
import { SetList, Set as SetSingle, Card as CardSingle } from '@tcgdex/sdk/interfaces' import { SetList, Set as SetSingle, Card as CardSingle } from '@tcgdex/sdk/interfaces'
import { getSet, getSets, isSetAvailable, setToSetSimple, setToSetSingle } from "../utils/setUtil" import { getSets, isSetAvailable, setToSetSimple, setToSetSingle } from '../utils/setUtil'
import { Languages, Set } from '../db/interfaces' import { Languages, Set } from '../db/interfaces'
import { Endpoint } from '../interfaces' import { Endpoint } from '../interfaces'
import { cardToCardSingle, getCards } from '../utils/cardUtil' import { cardToCardSingle, getCards } from '../utils/cardUtil'
export default class implements Endpoint<SetList, SetSingle, CardSingle, Array<Set>> { export default class implements Endpoint<SetList, SetSingle, CardSingle, Array<Set>> {
public constructor( public constructor(
private lang: keyof Languages private lang: keyof Languages
) {} ) {}
public async index(common: Array<Set>) { public async index(common: Array<Set>): Promise<SetList> {
const sets = common const sets = common
.sort((a, b) => a.releaseDate > b.releaseDate ? 1 : -1) .sort((a, b) => a.releaseDate > b.releaseDate ? 1 : -1)
@ -18,8 +19,8 @@ export default class implements Endpoint<SetList, SetSingle, CardSingle, Array<S
return tmp return tmp
} }
public async item(common: Array<Set>) { public async item(common: Array<Set>): Promise<Record<string, SetSingle>> {
const sets= await Promise.all(common const sets = await Promise.all(common
.map((set) => setToSetSingle(set, this.lang))) .map((set) => setToSetSingle(set, this.lang)))
const res: Record<string, SetSingle> = {} const res: Record<string, SetSingle> = {}
@ -31,21 +32,24 @@ export default class implements Endpoint<SetList, SetSingle, CardSingle, Array<S
return res return res
} }
public async common() { public async common(): Promise<Array<Set>> {
return (await getSets(undefined, this.lang)) return getSets(undefined, this.lang)
} }
public async sub(common: Array<Set>, item: string) { public async sub(common: Array<Set>, item: string): Promise<Record<string, CardSingle> | undefined> {
const set = common.find((s) => s.name[this.lang] === item || s.id === item) const set = common.find((s) => s.name[this.lang] === item || s.id === item)
if (!set || !isSetAvailable(set, this.lang)) return undefined if (!set || !isSetAvailable(set, this.lang)) {
return undefined
const lit = await getCards(this.lang, set)
const l: Record<string, CardSingle> = {}
for (let i of lit) {
l[i[0]] = await cardToCardSingle(i[0], i[1], this.lang)
} }
return l const lit = await getCards(this.lang, set)
const list: Record<string, CardSingle> = {}
for await (const card of lit) {
list[card[0]] = await cardToCardSingle(card[0], card[1], this.lang)
}
return list
} }
} }

View File

@ -4,31 +4,34 @@ import { Card, Languages } from '../db/interfaces'
import { Endpoint } from '../interfaces' import { Endpoint } from '../interfaces'
import { cardToCardSimple, getCards } from '../utils/cardUtil' import { cardToCardSimple, getCards } from '../utils/cardUtil'
export default class implements Endpoint<StringEndpointList, StringEndpoint, {}, Record<string, Array<[string, Card]>>> { export default class implements Endpoint<StringEndpointList, StringEndpoint, Record<string, unknown>, Record<string, Array<[string, Card]>>> {
public constructor( public constructor(
private lang: keyof Languages private lang: keyof Languages
) {} ) {}
public async index(common: Record<string, Array<[string, Card]>>) { public async index(common: Record<string, Array<[string, Card]>>): Promise<StringEndpointList> {
return Object.keys(common) return Object.keys(common)
} }
public async item(common: Record<string, Array<[string, Card]>>) { public async item(common: Record<string, Array<[string, Card]>>): Promise<Record<string, StringEndpoint>> {
const items: Record<string, StringEndpoint> = {} const items: Record<string, StringEndpoint> = {}
for (const key of Object.keys(common)) { for await (const key of Object.keys(common)) {
const val = common[key] const val = common[key]
items[key] = { items[key] = {
name: key, cards: await Promise.all(val.map(([id, card]) => cardToCardSimple(id, card, this.lang))),
cards: await Promise.all(val.map(([id, card]) => cardToCardSimple(id, card, this.lang))) name: key
} }
} }
return items return items
} }
public async common() { public async common(): Promise<Record<string, Array<[string, Card]>>> {
return (await getCards(this.lang)).reduce((p, c) => { return (await getCards(this.lang)).reduce((p, c) => {
const types = c[1].types?.map((t) => translate('types', t, this.lang) as string) const types = c[1].types?.map((t) => translate('types', t, this.lang) as string)
if (!types) return p if (!types) {
return p
}
for (const type of types) { for (const type of types) {
if (!p[type]) { if (!p[type]) {
p[type] = [] p[type] = []
@ -38,4 +41,5 @@ export default class implements Endpoint<StringEndpointList, StringEndpoint, {},
return p return p
}, {} as Record<string, Array<[string, Card]>>) }, {} as Record<string, Array<[string, Card]>>)
} }
} }

View File

@ -15,10 +15,9 @@ const VERSION = 'v2'
const paths = (await fs.readdir('./endpoints')).filter((f) => f.endsWith('.ts')) const paths = (await fs.readdir('./endpoints')).filter((f) => f.endsWith('.ts'))
console.log('Prefetching pictures') console.log('Prefetching pictures')
await fetchRemoteFile(`https://assets.tcgdex.net/datas.json`) await fetchRemoteFile('https://assets.tcgdex.net/datas.json')
await fs.rm(`./dist/${VERSION}/${lang}`, {recursive: true, force: true}) await fs.rm(`./dist/${VERSION}/${lang}`, {recursive: true, force: true})
console.log('Let\'s GO !') console.log('Let\'s GO !')
for (const file of paths) { for (const file of paths) {
const path = `./endpoints/${file}` const path = `./endpoints/${file}`

View File

@ -2,14 +2,15 @@
"name": "@tcgdex/compiler", "name": "@tcgdex/compiler",
"version": "2.0.0", "version": "2.0.0",
"repository": "https://github.com/tcgdex/compiler.git", "repository": "https://github.com/tcgdex/compiler.git",
"author": "Avior <florian@avior.me>", "author": "Avior <github@avior.me>",
"license": "MIT", "license": "MIT",
"private": false, "private": false,
"scripts": { "scripts": {
"db:compile": "cd db && tsc --project tsconfig.json", "db:compile": "cd db && tsc --project tsconfig.json",
"db:test": "cd db && tsc --noEmit --project tsconfig.json", "db:test": "cd db && tsc --noEmit --project tsconfig.json",
"start": "node --require ts-node/register --unhandled-rejections=strict main.ts", "start": "node --require ts-node/register --unhandled-rejections=strict main.ts",
"upload": "node --require ts-node/register --unhandled-rejections=strict upload.ts" "upload": "node --require ts-node/register --unhandled-rejections=strict upload.ts",
"lint": "eslint ./utils ./endpoints ./*.ts --ext .ts"
}, },
"dependencies": { "dependencies": {
"@dzeio/logger": "^2.0.0-alpha.0", "@dzeio/logger": "^2.0.0-alpha.0",
@ -26,5 +27,10 @@
"ssh2": "^0.8.9", "ssh2": "^0.8.9",
"ts-node": "^9.1.1", "ts-node": "^9.1.1",
"typescript": "^4.2.3" "typescript": "^4.2.3"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0",
"eslint": "^7.27.0"
} }
} }

View File

@ -1,5 +1,4 @@
import { config } from 'dotenv' import { config } from 'dotenv'
import path from 'path'
import { ConnectConfig } from 'ssh2' import { ConnectConfig } from 'ssh2'
import SFTPPromise from './SFTPPromise' import SFTPPromise from './SFTPPromise'
@ -7,26 +6,17 @@ config()
const sshConfig: ConnectConfig = { const sshConfig: ConnectConfig = {
host: process.env.UPLOAD_REMOTE, host: process.env.UPLOAD_REMOTE,
username: process.env.UPLOAD_USERNAME, password: process.env.UPLOAD_PASSWORD,
password: process.env.UPLOAD_PASSWORD , port: 22,
port: 22 username: process.env.UPLOAD_USERNAME
};
async function main() {
} }
main().then(msg => {
console.log(msg);
}).catch(err => {
console.log(`main error: ${err.message}`);
});
;(async () => { ;(async () => {
const client = new SFTPPromise(sshConfig) const client = new SFTPPromise(sshConfig)
// client.debug = true // client.debug = true
await client.connect() await client.connect()
const src = `${__dirname}/dist` const src = `${__dirname}/dist`
const dst = process.env.UPLOAD_DIST as string const dst = process.env.UPLOAD_DIST as string
await client.uploadDir(src, dst, /\.git/g) await client.uploadDir(src, dst, /\.git/gu)
process.exit(0) process.exit(0)
})() })()

View File

@ -1,12 +1,22 @@
import { setToSetSimple } from "./setUtil" /* eslint-disable sort-keys */
import { cardIsLegal, fetchRemoteFile, smartGlob } from "./util" import { setToSetSimple } from './setUtil'
import { cardIsLegal, fetchRemoteFile, smartGlob } from './util'
import { Set, SupportedLanguages, Card, Types } from 'db/interfaces' import { Set, SupportedLanguages, Card, Types } from 'db/interfaces'
import { Card as CardSingle, CardResume } from '@tcgdex/sdk/interfaces' import { Card as CardSingle, CardResume } from '@tcgdex/sdk/interfaces'
import translate from './translationUtil' import translate from './translationUtil'
type ObjectList<T = any> = Partial<Record<string, T>> export async function getCardPictures(cardId: string, card: Card, lang: SupportedLanguages): Promise<string | undefined> {
try {
type RemoteData = ObjectList<ObjectList<ObjectList<ObjectList<string>>>> 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<CardResume> { export async function cardToCardSimple(id: string, card: Card, lang: SupportedLanguages): Promise<CardResume> {
const cardName = card.name[lang] const cardName = card.name[lang]
@ -16,26 +26,14 @@ export async function cardToCardSimple(id: string, card: Card, lang: SupportedLa
const img = await getCardPictures(id, card, lang) const img = await getCardPictures(id, card, lang)
return { return {
id: `${card.set.id}-${id}`, id: `${card.set.id}-${id}`,
image: img,
localId: id, localId: id,
name: cardName, name: cardName
image: img
}
}
export async function getCardPictures(cardId: string, card: Card, lang: SupportedLanguages): Promise<string | undefined> {
try {
const file = await fetchRemoteFile(`https://assets.tcgdex.net/datas.json`)
const fileExists = !!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
} }
} }
// eslint-disable-next-line max-lines-per-function
export async function cardToCardSingle(localId: string, card: Card, lang: SupportedLanguages): Promise<CardSingle> { export async function cardToCardSingle(localId: string, card: Card, lang: SupportedLanguages): Promise<CardSingle> {
const image = await getCardPictures(localId, card, lang) const image = await getCardPictures(localId, card, lang)
if (!card.name[lang]) { if (!card.name[lang]) {
@ -43,22 +41,22 @@ export async function cardToCardSingle(localId: string, card: Card, lang: Suppor
} }
return { return {
id: `${card.set.id}-${localId}`,
localId: localId,
name: card.name[lang] as string,
image: image,
illustrator: card.illustrator,
rarity: translate('rarity', card.rarity, lang) as any,
category: translate('category', card.category, lang) as any, category: translate('category', card.category, lang) as any,
id: `${card.set.id}-${localId}`,
illustrator: card.illustrator,
image,
localId,
name: card.name[lang] as string,
rarity: translate('rarity', card.rarity, lang) as any,
set: await setToSetSimple(card.set, lang),
variants: { variants: {
normal: typeof card.variants?.normal === 'boolean' ? card.variants.normal : typeof card.set.variants?.normal === 'boolean' ? card.set.variants.normal : true,
reverse: typeof card.variants?.reverse === 'boolean' ? card.variants.reverse : typeof card.set.variants?.reverse === 'boolean' ? card.set.variants.reverse : true,
holo: typeof card.variants?.holo === 'boolean' ? card.variants.holo : typeof card.set.variants?.holo === 'boolean' ? card.set.variants.holo : true,
firstEdition: typeof card.variants?.firstEdition === 'boolean' ? card.variants.firstEdition : typeof card.set.variants?.firstEdition === 'boolean' ? card.set.variants.firstEdition : false, firstEdition: typeof card.variants?.firstEdition === 'boolean' ? card.variants.firstEdition : typeof card.set.variants?.firstEdition === 'boolean' ? card.set.variants.firstEdition : false,
holo: typeof card.variants?.holo === 'boolean' ? card.variants.holo : typeof card.set.variants?.holo === 'boolean' ? card.set.variants.holo : true,
normal: typeof card.variants?.normal === 'boolean' ? card.variants.normal : typeof card.set.variants?.normal === 'boolean' ? card.set.variants.normal : true,
reverse: typeof card.variants?.reverse === 'boolean' ? card.variants.reverse : typeof card.set.variants?.reverse === 'boolean' ? card.set.variants.reverse : true
}, },
set: await setToSetSimple(card.set, lang),
dexId: card.dexId, dexId: card.dexId,
hp: card.hp, hp: card.hp,
@ -86,7 +84,6 @@ export async function cardToCardSingle(localId: string, card: Card, lang: Suppor
effect: el.effect ? el.effect[lang] : undefined, effect: el.effect ? el.effect[lang] : undefined,
damage: el.damage damage: el.damage
})), })),
weaknesses: card.weaknesses?.map((el) => ({ weaknesses: card.weaknesses?.map((el) => ({
type: translate('types', el.type, lang) as Types, type: translate('types', el.type, lang) as Types,
value: el.value value: el.value
@ -124,7 +121,7 @@ export async function getCard(serie: string, setName: string, id: string): Promi
} }
export async function getCards(lang: SupportedLanguages, set?: Set): Promise<Array<[string, Card]>> { export async function getCards(lang: SupportedLanguages, set?: Set): Promise<Array<[string, Card]>> {
const cards = (await smartGlob(`./db/data/${(set && set.serie.name.en) ?? '*'}/${(set && set.name.en) ?? '*'}/*.js`)) const cards = await smartGlob(`./db/data/${(set && set.serie.name.en) ?? '*'}/${(set && set.name.en) ?? '*'}/*.js`)
const list: Array<[string, Card]> = [] const list: Array<[string, Card]> = []
for (const path of cards) { for (const path of cards) {
const id = path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.')) const id = path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.'))
@ -146,12 +143,11 @@ export async function getCards(lang: SupportedLanguages, set?: Set): Promise<Arr
// Sort by id when possible // Sort by id when possible
return list.sort(([a], [b]) => { return list.sort(([a], [b]) => {
const ra = parseInt(a) const ra = parseInt(a, 10)
const rb = parseInt(b) const rb = parseInt(b, 10)
if (!isNaN(ra) && !isNaN(rb)) { if (!isNaN(ra) && !isNaN(rb)) {
return ra - rb return ra - rb
} else {
return a >= b ? 1 : -1
} }
return a >= b ? 1 : -1
}) })
} }

View File

@ -1,44 +1,43 @@
import { smartGlob } from "./util" import { smartGlob } from './util'
import { setToSetSimple, getSets, getSet, isSetAvailable } from "./setUtil" import { setToSetSimple, getSets } from './setUtil'
import { Serie, SupportedLanguages, Set } from 'db/interfaces' import { Serie, SupportedLanguages, Set } from 'db/interfaces'
import { Serie as SerieSingle, SerieResume } from '@tcgdex/sdk/interfaces' import { Serie as SerieSingle, SerieResume } from '@tcgdex/sdk/interfaces'
export async function getSerie(name: string): Promise<Serie> {
return (await import(`../db/data/${name}.js`)).default
}
export async function isSerieAvailable(serie: Serie, lang: SupportedLanguages): Promise<boolean> {
if (!serie.name[lang]) {
return false
}
const sets = await getSets(serie.name.en, lang)
return sets.length > 0
}
export async function getSeries(lang: SupportedLanguages): Promise<Array<Serie>> { export async function getSeries(lang: SupportedLanguages): Promise<Array<Serie>> {
let series: Array<Serie> = (await Promise.all((await smartGlob('./db/data/*.js')) let series: Array<Serie> = (await Promise.all((await smartGlob('./db/data/*.js'))
//Find Serie's name // Find Serie's name
.map((it) => it.substring(it.lastIndexOf('/') + 1, it.length - 3)) .map((it) => it.substring(it.lastIndexOf('/') + 1, it.length - 3))
// Fetch the Serie // Fetch the Serie
.map((it) => getSerie(it)))) .map((it) => getSerie(it))))
// Filter the serie if no name's exists in the selected lang // Filter the serie if no name's exists in the selected lang
.filter((serie) => !!serie.name[lang]) .filter((serie) => Boolean(serie.name[lang]))
// Filter available series // Filter available series
const isAvailable = await Promise.all(series.map((serie) => isSerieAvailable(serie, lang))) const isAvailable = await Promise.all(series.map((serie) => isSerieAvailable(serie, lang)))
series = series.filter((_, index) => isAvailable[index]) series = series.filter((_, index) => isAvailable[index])
// Sort series by the first set release date // Sort series by the first set release date
const tmp: Array<[Serie, Set | undefined]> = await Promise.all(series.map(async (it) => { const tmp: Array<[Serie, Set | undefined]> = await Promise.all(series.map(async (it) => [
return [ it,
it, (await getSets(it.name.en, lang))
(await getSets(it.name.en, lang)) .reduce<Set | undefined>((p, c) => p ? p.releaseDate < c.releaseDate ? p : c : c, undefined) as Set
.reduce<Set | undefined>((p, c) => p ? p.releaseDate < c.releaseDate ? p : c : c, undefined) as Set] as [Serie, 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]) 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 getSerie(name: string): Promise<Serie> {
return (await import(`../db/data/${name}.js`)).default
}
export async function isSerieAvailable(serie: Serie, lang: SupportedLanguages) {
if (!serie.name[lang]) {
return false
}
const sets = (await getSets(serie.name.en, lang))
return sets.length > 0
}
export async function serieToSerieSimple(serie: Serie, lang: SupportedLanguages): Promise<SerieResume> { export async function serieToSerieSimple(serie: Serie, lang: SupportedLanguages): Promise<SerieResume> {
return { return {
id: serie.id, id: serie.id,
@ -49,10 +48,8 @@ export async function serieToSerieSimple(serie: Serie, lang: SupportedLanguages)
export async function serieToSerieSingle(serie: Serie, lang: SupportedLanguages): Promise<SerieSingle> { export async function serieToSerieSingle(serie: Serie, lang: SupportedLanguages): Promise<SerieSingle> {
const setsTmp = await getSets(serie.name.en, lang) const setsTmp = await getSets(serie.name.en, lang)
const sets = await Promise.all(setsTmp const sets = await Promise.all(setsTmp
.sort((a, b) => { .sort((a, b) => a.releaseDate > b.releaseDate ? 1 : -1)
return a.releaseDate > b.releaseDate ? 1 : -1 .map((el) => setToSetSimple(el, lang)))
})
.map(el => setToSetSimple(el, lang)))
return { return {
id: serie.id, id: serie.id,
name: serie.name[lang] as string, name: serie.name[lang] as string,

View File

@ -1,5 +1,5 @@
import { Set, SupportedLanguages } from "db/interfaces" import { Set, SupportedLanguages } from 'db/interfaces'
import { fetchRemoteFile, setIsLegal, smartGlob } from "./util" import { fetchRemoteFile, setIsLegal, smartGlob } from './util'
import { cardToCardSimple, getCards } from './cardUtil' import { cardToCardSimple, getCards } from './cardUtil'
import { SetResume, Set as SetSingle } from '@tcgdex/sdk/interfaces' import { SetResume, Set as SetSingle } from '@tcgdex/sdk/interfaces'
@ -9,52 +9,46 @@ interface t {
const setCache: t = {} const setCache: t = {}
// Dont use cache as it wont necessary have them all export function isSetAvailable(set: Set, lang: SupportedLanguages): boolean {
export async function getSets(serie = '*', lang: SupportedLanguages): Promise<Array<Set>> { return lang in set.name
// list sets names
const rawSets = (await smartGlob(`./db/data/${serie}/*.js`)).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) => {
return a.releaseDate > b.releaseDate ? 1 : -1
})
return sets
} }
/** /**
* Return the set * Return the set
* @param name the name of the set (don't include.js/.ts) * @param name the name of the set (don't include.js/.ts)
*/ */
export async function getSet(name: string, serie = '*', lang: SupportedLanguages): Promise<Set> { export async function getSet(name: string, serie = '*'): Promise<Set> {
if (!setCache[name]) { if (!setCache[name]) {
try { try {
const [path] = await smartGlob(`./db/data/${serie}/${name}.js`) const [path] = await smartGlob(`./db/data/${serie}/${name}.js`)
setCache[name] = (await import(path.replace('./', '../'))).default setCache[name] = (await import(path.replace('./', '../'))).default
} catch (e) { } catch (error) {
const set = (await getSets(undefined, lang)).find((s) => s.id === name) console.error(error)
if (set) { console.error(`Error trying to import importing (${`db/data/${serie}/${name}.js`})`)
return set
}
console.error(e)
console.error(`Error trying to import importing (${`db/data/*/${name}.js`})`)
process.exit(1) process.exit(1)
} }
} }
return setCache[name] return setCache[name]
} }
export function isSetAvailable(set: Set, lang: SupportedLanguages): boolean { // Dont use cache as it wont necessary have them all
return lang in set.name && lang in set.serie.name export async function getSets(serie = '*', lang: SupportedLanguages): Promise<Array<Set>> {
// list sets names
const rawSets = (await smartGlob(`./db/data/${serie}/*.js`)).map((set) => set.substring(set.lastIndexOf('/') + 1, set.lastIndexOf('.')))
// Fetch sets
const sets = (await Promise.all(rawSets.map((set) => getSet(set, serie))))
// 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]> { export async function getSetPictures(set: Set, lang: SupportedLanguages): Promise<[string | undefined, string | undefined]> {
try { try {
const file = await fetchRemoteFile(`https://assets.tcgdex.net/datas.json`) 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 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 const symbolExists = file.univ?.[set.serie.id]?.[set.id]?.symbol ? `https://assets.tcgdex.net/univ/${set.serie.id}/${set.id}/symbol` : undefined
return [ return [
logoExists, logoExists,
symbolExists symbolExists
@ -68,14 +62,14 @@ export async function setToSetSimple(set: Set, lang: SupportedLanguages): Promis
const cards = await getCards(lang, set) const cards = await getCards(lang, set)
const pics = await getSetPictures(set, lang) const pics = await getSetPictures(set, lang)
return { return {
cardCount: {
official: set.cardCount.official,
total: Math.max(set.cardCount.official, cards.length)
},
id: set.id, id: set.id,
logo: pics[0], logo: pics[0],
symbol: pics[1],
name: set.name[lang] as string, name: set.name[lang] as string,
cardCount: { symbol: pics[1]
total: Math.max(set.cardCount.official, cards.length),
official: set.cardCount.official
},
} }
} }
@ -83,28 +77,28 @@ export async function setToSetSingle(set: Set, lang: SupportedLanguages): Promis
const cards = await getCards(lang, set) const cards = await getCards(lang, set)
const pics = await getSetPictures(set, lang) const pics = await getSetPictures(set, lang)
return { return {
name: set.name[lang] as string, cardCount: {
firstEd: cards.reduce((count, card) => count + (card[1].variants?.firstEdition ?? set.variants?.firstEdition ? 1 : 0), 0),
holo: cards.reduce((count, card) => count + (card[1].variants?.holo ?? set.variants?.holo ? 1 : 0), 0),
normal: cards.reduce((count, card) => count + (card[1].variants?.normal ?? set.variants?.normal ? 1 : 0), 0),
official: set.cardCount.official,
reverse: cards.reduce((count, card) => count + (card[1].variants?.reverse ?? set.variants?.reverse ? 1 : 0), 0),
total: Math.max(set.cardCount.official, cards.length)
},
cards: await Promise.all(cards.map(([id, card]) => cardToCardSimple(id, card, lang))),
id: set.id, id: set.id,
legal: {
expanded: setIsLegal('expanded', set),
standard: setIsLegal('standard', set)
},
logo: pics[0],
name: set.name[lang] as string,
releaseDate: set.releaseDate,
serie: { serie: {
id: set.serie.id, id: set.serie.id,
name: set.serie.name[lang] as string name: set.serie.name[lang] as string
}, },
tcgOnline: set.tcgOnline,
cardCount: {
total: Math.max(set.cardCount.official, cards.length),
official: set.cardCount.official,
normal: cards.reduce((count, card) => count + (card[1].variants?.normal ?? set.variants?.normal ? 1 : 0), 0),
reverse: cards.reduce((count, card) => count + (card[1].variants?.reverse ?? set.variants?.reverse ? 1 : 0), 0),
holo: cards.reduce((count, card) => count + (card[1].variants?.holo ?? set.variants?.holo ? 1 : 0), 0),
firstEd: cards.reduce((count, card) => count + (card[1].variants?.firstEdition ?? set.variants?.firstEdition ? 1 : 0), 0),
},
releaseDate: set.releaseDate,
legal: {
standard: setIsLegal('standard', set),
expanded: setIsLegal('expanded', set)
},
logo: pics[0],
symbol: pics[1], symbol: pics[1],
cards: await Promise.all(cards.map(([id, card]) => cardToCardSimple(id, card, lang))) tcgOnline: set.tcgOnline
} }
} }

View File

@ -4,72 +4,73 @@ type translatable = 'types' | 'rarity' | 'stage' | 'category' | 'suffix' | 'abil
const translations: Record<string, Record<translatable, Record<string, string>>> = { const translations: Record<string, Record<translatable, Record<string, string>>> = {
fr: { fr: {
types: {
'Colorless': 'Incolore',
'Darkness': 'Obscurité',
'Dragon': 'Dragon',
'Fairy': 'Fée',
'Fighting': 'Combat',
'Fire': 'Feu',
'Grass': 'Plante',
'Lightning': 'Électrique',
'Metal': 'Métal',
'Psychic': 'Psy',
'Water': 'Eau'
},
rarity: {
'None': 'Rien',
'Common': 'Commune',
'Uncommon': 'Non Commune',
'Rare': 'Rare',
'Ultra Rare': 'Ultra Rare',
'Secret Rare': 'Magnifique rare'
},
stage: {
"Basic": 'Base',
"BREAK": 'TURBO',
"LEVEL-UP": 'Niveau Sup',
"MEGA": 'MÉGA',
"RESTORED": 'RECRÉE',
"Stage1": 'Niveau1',
"Stage2": 'Biveau2',
"VMAX": 'VMAX'
},
category: {
Pokemon: 'Pokémon',
Trainer: 'Dresseur',
Energy: 'Énergie'
},
suffix: {
'EX': 'EX',
'GX': 'GX',
'V': 'V',
'Legend': 'LÉGENDE',
'Prime': 'Prime',
'SP': 'SP',
'TAG TEAM-GX': 'TAG TEAM-GX',
},
abilityType: { abilityType: {
'Pokemon Power': 'Pouvoir Pokémon', 'Ability': 'Talent',
'Ancient Trait': 'Trait Antique',
'Poke-BODY': 'Poké-BODY', 'Poke-BODY': 'Poké-BODY',
'Poke-POWER': 'Poké-POWER', 'Poke-POWER': 'Poké-POWER',
'Ability': 'Talent', 'Pokemon Power': 'Pouvoir Pokémon'
'Ancient Trait': 'Trait Antique'
}, },
trainerType: { category: {
'Supporter': 'Supporter', Energy: 'Énergie',
'Item': 'Objet', Pokemon: 'Pokémon',
'Stadium': 'Stade', Trainer: 'Dresseur'
'Tool': 'Outil',
'Ace Spec': 'High-Tech',
'Technical Machine': 'Machine Technique',
'Goldenred Game Corner': 'Salle de jeu de Doublonville',
'Rocket\'s Secret Machine': 'Machine secrète des Rocket'
}, },
energyType: { energyType: {
Normal: 'Normal', Normal: 'Normal',
Special: 'Spécial' Special: 'Spécial'
}, },
rarity: {
'Common': 'Commune',
'None': 'Rien',
'Rare': 'Rare',
'Secret Rare': 'Magnifique rare',
'Ultra Rare': 'Ultra Rare',
'Uncommon': 'Non Commune'
},
stage: {
'BREAK': 'TURBO',
'Basic': 'Base',
'LEVEL-UP': 'Niveau Sup',
'MEGA': 'MÉGA',
'RESTORED': 'RECRÉE',
'Stage1': 'Niveau1',
'Stage2': 'Niveau2',
'VMAX': 'VMAX'
},
suffix: {
'EX': 'EX',
'GX': 'GX',
'Legend': 'LÉGENDE',
'Prime': 'Prime',
'SP': 'SP',
'TAG TEAM-GX': 'TAG TEAM-GX',
'V': 'V'
},
trainerType: {
'Ace Spec': 'High-Tech',
'Goldenred Game Corner': 'Salle de jeu de Doublonville',
'Item': 'Objet',
'Rocket\'s Secret Machine': 'Machine secrète des Rocket',
'Stadium': 'Stade',
'Supporter': 'Supporter',
'Technical Machine': 'Machine Technique',
'Tool': 'Outil'
},
types: {
Colorless: 'Incolore',
Darkness: 'Obscurité',
Dragon: 'Dragon',
Fairy: 'Fée',
Fighting: 'Combat',
Fire: 'Feu',
Grass: 'Plante',
Lightning: 'Électrique',
Metal: 'Métal',
Psychic: 'Psy',
Water: 'Eau'
}
} }
} }
@ -82,7 +83,7 @@ export default function translate(item: translatable, key: string | undefined, l
} }
const res = translations[lang]?.[item]?.[key] const res = translations[lang]?.[item]?.[key]
if (!res) { if (!res) {
throw new Error(`Could not find translation for ${lang}${item}.${key}`); throw new Error(`Could not find translation for ${lang}${item}.${key}`)
} }
return res return res
} }

View File

@ -4,7 +4,11 @@ import fetch from 'node-fetch'
import * as legals from '../db/legals' import * as legals from '../db/legals'
export function urlize(str: string): string { export function urlize(str: string): string {
return str.replace('?', '%3F').normalize('NFC').replace(/["'\u0300-\u036f]/g, "") return str
.replace('?', '%3F')
.normalize('NFC')
// eslint-disable-next-line no-misleading-character-class
.replace(/["'\u0300-\u036f]/gu, '')
} }
interface fileCacheInterface { interface fileCacheInterface {
@ -25,19 +29,21 @@ export async function fetchRemoteFile<T = any>(url: string): Promise<T> {
const globCache: Record<string, Array<string>> = {} const globCache: Record<string, Array<string>> = {}
export async function smartGlob(query: string) { export async function smartGlob(query: string): Promise<Array<string>> {
if (!globCache[query]) { if (!globCache[query]) {
globCache[query] = await new Promise((res) => glob(query, (err, matches) => res(matches))) globCache[query] = await new Promise((res) => {
glob(query, (err, matches) => res(matches))
})
} }
return globCache[query] return globCache[query]
} }
export function cardIsLegal(type: 'standard' | 'expanded', card: Card, localId: string) { export function cardIsLegal(type: 'standard' | 'expanded', card: Card, localId: string): boolean {
const legal = legals[type] const legal = legals[type]
if ( if (
legal.includes.series.includes(card.set.serie.id) || legal.includes.series.includes(card.set.serie.id) ||
legal.includes.sets.includes(card.set.id) || legal.includes.sets.includes(card.set.id) ||
(card.regulationMark && legal.includes.regulationMark.includes(card.regulationMark)) card.regulationMark && legal.includes.regulationMark.includes(card.regulationMark)
) { ) {
return !( return !(
legal.excludes.sets.includes(card.set.id) || legal.excludes.sets.includes(card.set.id) ||
@ -47,7 +53,7 @@ export function cardIsLegal(type: 'standard' | 'expanded', card: Card, localId:
return false return false
} }
export function setIsLegal(type: 'standard' | 'expanded', set: Set) { export function setIsLegal(type: 'standard' | 'expanded', set: Set): boolean {
const legal = legals[type] const legal = legals[type]
if ( if (
legal.includes.series.includes(set.serie.id) || legal.includes.series.includes(set.serie.id) ||

927
yarn.lock

File diff suppressed because it is too large Load Diff