init: monorepo 初始化 - ygo-sdk + ygo-deck + ygo-deck-server

This commit is contained in:
yami
2026-07-19 18:10:38 +08:00
commit a86d77d428
373 changed files with 33422 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
node_modules/
dist/
.umi/
.env
*.log
.DS_Store
+16
View File
@@ -0,0 +1,16 @@
{
"name": "ygo-deck",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"build:sdk": "cd packages/ygo-sdk && npm run build",
"build:server": "cd packages/ygo-deck-server && npm run build",
"build:fe": "cd packages/ygo-deck && npm run build",
"build": "npm run build:sdk && npm run build:server && npm run build:fe",
"dev:server": "cd packages/ygo-deck-server && npm run start:dev",
"dev:fe": "cd packages/ygo-deck && npm start",
"install:all": "npm install"
}
}
+24
View File
@@ -0,0 +1,24 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
+36
View File
@@ -0,0 +1,36 @@
# compiled output
/dist
/node_modules
/file-cache
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
+4
View File
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}
+42
View File
@@ -0,0 +1,42 @@
# YGO DECK Server 开发文档
## 初始化
### 安装前置依赖
```shell
sudo apt-get install imagemagick # 图片处理工具
sudo apt install mysql-server
```
安装node依赖
```shell
yarn # 如果报错使用cnpm i
```
### 更新卡片数据库
更新本地的卡片数据库
```shell
yarn cdb
```
### 更新卡片图库
将比较全的卡图文件夹放在public/pics/500里面,要求图片为jpg格式
你可以使用爬虫来获取最新的卡图
## 开发
设计数据库的时候记得先学习数据库的五大范式哦
## 部署
```shell
yarn
yarn build
pm2 start dist/src/main.js --name BE
```
+16
View File
@@ -0,0 +1,16 @@
import * as fs from 'fs';
import * as path from 'path';
const isProd = process.env.NODE_ENV === 'production';
function parseEnv() {
const localEnv = path.resolve('.env');
const prodEnv = path.resolve('.env.prod');
if (!fs.existsSync(localEnv) && !fs.existsSync(prodEnv)) {
throw new Error('缺少环境配置文件');
}
const filePath = isProd && fs.existsSync(prodEnv) ? prodEnv : localEnv;
return { path: filePath };
}
export default parseEnv();
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
{
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}
+10
View File
@@ -0,0 +1,10 @@
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "18032910ym",
"database": "ygo",
"entities": ["dist/**/*.entity{.ts,.js}"],
"synchronize": true
}
File diff suppressed because it is too large Load Diff
+97
View File
@@ -0,0 +1,97 @@
{
"name": "ygo-pics",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"start:serve": "git pull origin master && yarn && yarn build && pm2 reload dist/src/main.js --name BE",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"cdb": "./scripts/cards.cdb.sh",
"pic": "./scripts/get_pics.sh"
},
"dependencies": {
"@nestjs/common": "^8.0.0",
"@nestjs/config": "^2.0.0",
"@nestjs/core": "^8.0.0",
"@nestjs/jwt": "^8.0.0",
"@nestjs/mapped-types": "*",
"@nestjs/passport": "^8.2.1",
"@nestjs/platform-express": "^8.0.0",
"@nestjs/swagger": "^5.2.1",
"@nestjs/typeorm": "^8.0.3",
"@types/passport": "^1.0.7",
"@types/passport-jwt": "^3.0.6",
"@types/passport-local": "^1.0.34",
"bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1",
"cookie-parser": "^1.4.6",
"gm": "^1.23.1",
"install": "^0.13.0",
"mysql2": "^2.3.3",
"passport": "^0.5.2",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"stream": "^0.0.2",
"typeorm": "^0.3.6",
"ygo-card-node": "^1.4.4",
"ygo-sdk": "^1.0.5"
},
"devDependencies": {
"@nestjs/cli": "^8.0.0",
"@nestjs/schematics": "^8.0.0",
"@nestjs/testing": "^8.0.0",
"@types/express": "^4.17.13",
"@types/jest": "27.4.0",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.2.5",
"pm2": "^5.2.0",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "^27.0.3",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "^3.10.1",
"typescript": "^4.3.5"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

@@ -0,0 +1,3 @@
git pull origin master
yarn
yarn build
+6
View File
@@ -0,0 +1,6 @@
wget -P ./locales/en-US/ -N https://koishi.pro/ygopro/locales/en-US/cards.cdb
wget -P ./locales/es-ES/ -N https://koishi.pro/ygopro/locales/es-ES/cards.cdb
wget -P ./locales/ja-JP/ -N https://koishi.pro/ygopro/locales/ja-JP/cards.cdb
wget -P ./locales/ko-KR/ -N https://koishi.pro/ygopro/locales/ko-KR/cards.cdb
wget -P ./locales/pt-BR/ -N https://koishi.pro/ygopro/locales/pt-BR/cards.cdb
wget -P ./locales/zh-CN/ -N https://koishi.pro/ygopro/locales/zh-CN/cards.cdb
@@ -0,0 +1,2 @@
cd public
git clone https://gitee.com/ymssx/pics.git
+2
View File
@@ -0,0 +1,2 @@
sudo apt-get install imagemagick
sudo apt install mysql-server
@@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});
@@ -0,0 +1,41 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const stream = require('stream');
import { Controller, Get, Query, Res, Request } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('api')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get('data')
async getCardData(@Query() query): Promise<any> {
const { id, lang = 'zh-CN' } = query;
return await this.appService.getCardDataWithHash(id, lang);
}
@Get('search')
async searchCardData(@Query() query): Promise<any> {
const { keywords } = query;
return await this.appService.searchData({ keywords });
}
@Get('data/list')
async getMultiCardData(@Query() query): Promise<any> {
const { ids, lang = 'zh-CN' } = query;
return await this.appService.getMultiCardData(ids, lang);
}
@Get('pic')
async getCardPic(@Query() query, @Res() res: any) {
const { id, size = 100, hash } = query;
const buffer = await this.appService.getCardPic(id, Number(size), hash);
const bufferStream = new stream.PassThrough();
bufferStream.end(buffer);
bufferStream.pipe(res);
}
}
@@ -0,0 +1,25 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DeckModule } from './deck/deck.module';
import { DeckCommentModule } from './deck-comment/deck-comment.module';
import { AuthModule } from './auth/auth.module';
import { DeckRelationModule } from './deck-relation/deck-relation.module';
import { DeckStatisticsModule } from './deck-statistics/deck-statistics.module';
@Module({
imports: [
TypeOrmModule.forRoot(),
UserModule,
DeckModule,
DeckCommentModule,
AuthModule,
DeckRelationModule,
DeckStatisticsModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
+144
View File
@@ -0,0 +1,144 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { queryDataList, getData, getCard, variation } from 'ygo-card-node';
import { getPicHash, checkPicHash } from './service/lock';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const gm = require('gm');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { promises } = require('fs');
// TODO: 这里和下面都要替换成动态的国际化目录 ./locales/{lang}/cards.cdb
const DB_PATH = './src/cards.cdb';
interface Rules {
keywords: string;
}
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
async getCardPic(id: string, size = 100, hash: string): Promise<Buffer> {
if (size > 1000) {
throw new HttpException('size too large', HttpStatus.PAYLOAD_TOO_LARGE);
}
// const checkRes = checkPicHash(hash, id, size);
// if (Array.isArray(checkRes)) {
// const [reason, code] = checkRes;
// throw new HttpException(reason, code);
// }
// if (checkRes !== true) {
// throw new HttpException(
// 'Unknown Error',
// HttpStatus.INTERNAL_SERVER_ERROR,
// );
// }
const filePath = `./file-cache/pic/${id}.jpg`;
const exists = await promises
.access(filePath)
.then(() => true)
.catch((_) => false);
if (exists) {
return new Promise((resolve, reject) => {
gm(filePath).toBuffer('jpg', (err, buffer) => {
if (err) return reject(err);
resolve(buffer);
});
});
}
const data = await this.getCardData(id);
const sortedData = variation(data);
const buffer = await getCard(
{
...sortedData,
name: '',
desc: '',
lb_desc: '',
},
{
picPath: `./public/pics/500/${id}.jpg`,
moldPath: './public/mold/',
type: 'image/jpeg',
size,
},
);
gm(buffer, `${id}.jpg`).write(`./file-cache/pic/${id}.jpg`, function (err) {
if (err) {
console.error(err);
}
});
return buffer;
}
async getCardData(id: string, lang = 'zh-CN'): Promise<any> {
if (!id) {
throw new HttpException("id can'not be empty", HttpStatus.FORBIDDEN);
}
const data = await getData(`./locales/${lang}/cards.cdb`, id);
if (!data) {
throw new HttpException('no data', HttpStatus.NOT_FOUND);
}
return data;
}
async getCardDataWithHash(id: string, lang = 'zh-CN'): Promise<any> {
const data = await this.getCardData(id, lang);
return {
...data,
hash: getPicHash([id], 500),
};
}
async getMultiCardData(ids: string, lang = 'zh-CN'): Promise<any[]> {
if (!ids) {
throw new HttpException("id can'not be empty", HttpStatus.FORBIDDEN);
}
const list = ids.split(',');
if (list.length > 100) {
throw new HttpException(
'list length is too long',
HttpStatus.PAYLOAD_TOO_LARGE,
);
}
const jobs = list.map((id) => {
return getData(`./locales/${lang}/cards.cdb`, id)
.then((data) => {
if (data) {
return data;
} else {
throw new HttpException(`${id} has no data`, HttpStatus.NOT_FOUND);
}
})
.catch((err) => {
throw new HttpException(`${err}`, HttpStatus.INTERNAL_SERVER_ERROR);
});
});
return Promise.all(jobs);
}
async searchData(rules: Rules) {
const { keywords } = rules;
const SQL = `
SELECT *
FROM texts t, datas d
WHERE
t.id = d.id
AND t.name LIKE '%${keywords}%'
`;
return queryDataList(DB_PATH, SQL);
}
}

Some files were not shown because too many files have changed in this diff Show More