Commit 0df1c6b5 authored by nanahira's avatar nanahira

add /api/novelai-auth

parent df1ebd2a
Pipeline #17332 passed with stages
in 3 minutes and 45 seconds
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -46,7 +46,7 @@
"rxjs": "^6.6.3",
"sqlite3": "^5.0.0",
"swagger-ui-express": "^4.1.6",
"typeorm": "^0.2.37",
"typeorm": "0.2.45",
"underscore": "^1.12.0"
},
"devDependencies": {
......
import { Test, TestingModule } from '@nestjs/testing';
import { AccountService } from './account.service';
describe('AccountService', () => {
let service: AccountService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AccountService],
}).compile();
service = module.get<AccountService>(AccountService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import {
HttpException,
HttpService,
Inject,
Injectable,
InternalServerErrorException,
} from '@nestjs/common';
import { config } from 'src/config';
export interface MycardUser {
id: number;
username: string;
name: string;
email: string;
password_hash: string;
salt: string;
active: boolean;
admin: boolean;
avatar: string;
locale: string;
registration_ip_address: string;
ip_address: string;
created_at: string;
updated_at: string;
}
@Injectable()
export class AccountService {
private url = config.accountsUrl + '/authUser';
constructor(private http: HttpService) {}
async getUserFromToken(token: string): Promise<MycardUser> {
try {
const result = await this.http
.get<MycardUser & { error?: string }>(this.url, {
responseType: 'json',
headers: {
Authorization: `Bearer ${token}`,
},
})
.toPromise();
if (result.status >= 400) {
throw new HttpException(result.data?.error, result.status);
}
return result.data;
} catch (e) {
throw new InternalServerErrorException(e);
}
}
async checkToken(token: string) {
if (typeof token !== 'string') {
throw new HttpException('Invalid token', 400);
}
if (token.startsWith('Bearer ')) {
token = token.slice(7);
}
return this.getUserFromToken(token);
}
}
......@@ -4,31 +4,33 @@ import {
Controller,
ForbiddenException,
Get,
Headers,
HttpCode,
HttpException,
InternalServerErrorException,
NotFoundException,
Param,
ParseIntPipe,
Post,
Query,
Req,
Res,
UploadedFile,
UploadedFiles,
UseInterceptors,
} from '@nestjs/common';
import express from 'express';
import { AppService } from './app.service';
import { UserInfo } from './entities/mycard/UserInfo';
import { config } from './config';
import { AnyFilesInterceptor, FileInterceptor } from '@nestjs/platform-express';
import { FileInterceptor } from '@nestjs/platform-express';
import { HttpResponseService } from './http-response/http-response.service';
import { CodeResponseDto } from './dto/CodeResponse.dto';
import multer from 'multer';
import cryptoRandomString from 'crypto-random-string';
import { join } from 'path';
import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
import {
ApiBody,
ApiConsumes,
ApiHeader,
ApiOkResponse,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { FileUploadDto } from './dto/FileUploadDto';
import { HomePageMatchCountDto } from './dto/HomePageMatchCount.dto';
......@@ -271,4 +273,12 @@ export class AppController {
async getLastMonthBattleCount(): Promise<HomePageMatchCountDto> {
return this.appService.getLastMonthBattleCount();
}
@Get('novelai-auth')
@ApiOperation({ summary: 'novelai 用认证' })
@ApiHeader({ name: 'Authorization' })
@ApiOkResponse({ type: CodeResponseDto })
async novelaiAuth(@Headers('Authorization') token: string) {
return this.appService.novelaiAuth(token);
}
}
......@@ -52,6 +52,7 @@ import { CardInfoService } from './card-info/card-info.service';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
import { AthleticCheckerService } from './athletic-checker/athletic-checker.service';
import { AccountService } from './account/account.service';
const ygoproEntities = [YGOProDatabaseDatas, YGOProDatabaseTexts];
const mycardEntities = [
......@@ -138,6 +139,7 @@ const mycardEntities = [
EloService,
CardInfoService,
AthleticCheckerService,
AccountService,
],
})
export class AppModule {}
import { Injectable, NotFoundException } from '@nestjs/common';
import { HttpException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectConnection, InjectEntityManager } from '@nestjs/typeorm';
import {
Brackets,
......@@ -38,6 +38,8 @@ import { EloService } from './elo/elo.service';
import { CardInfoService } from './card-info/card-info.service';
import { AthleticCheckerService } from './athletic-checker/athletic-checker.service';
import { HomePageMatchCountDto } from './dto/HomePageMatchCount.dto';
import { AccountService } from './account/account.service';
import { CodeResponseDto } from './dto/CodeResponse.dto';
const attrOffset = 1010;
const raceOffset = 1020;
......@@ -122,6 +124,7 @@ export class AppService {
private eloService: EloService,
private cardInfoService: CardInfoService,
private athleticCheckerService: AthleticCheckerService,
private accountService: AccountService,
) {
this.log.setContext('ygopro-arena-revive');
this.chineseDirtyFilter = new Filter({
......@@ -1777,4 +1780,24 @@ export class AppService {
});
return new HomePageMatchCountDto(count);
}
async novelaiAuth(token: string) {
const user = await this.accountService.checkToken(token);
if (user.admin) {
return new CodeResponseDto(200);
}
return this.mcdb.transaction(async (edb) => {
const userInfo = await edb.getRepository(UserInfo).findOne({
select: ['username', 'exp'],
where: { username: user.username },
});
if (!userInfo || userInfo.exp < config.novelaiCost) {
throw new HttpException(new CodeResponseDto(402), 402);
}
await edb
.getRepository(UserInfo)
.decrement({ username: user.username }, 'exp', config.novelaiCost);
return new CodeResponseDto(200);
});
}
}
......@@ -8,6 +8,8 @@ export interface Config {
deckIdentifierPath: string;
analyzerHost: string;
enableSchedule: boolean;
accountsUrl: string;
novelaiCost: number;
}
export const athleticCheckConfig = {
......@@ -31,4 +33,9 @@ export const config: Config = {
deckIdentifierPath: process.env.DECK_IDENTIFIER_PATH,
analyzerHost: process.env.ANALYZER_HOST,
enableSchedule: !process.env.NO_SCHEDULE,
accountsUrl:
process.env.ACCOUNTS_URL || 'https://sapi.moecube.com:444/accounts',
novelaiCost: process.env.NOVELAI_COST
? parseInt(process.env.NOVELAI_COST)
: 3,
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment