Initial commit
This commit is contained in:
+15
-2
@@ -1,10 +1,23 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { GamesModule } from './games/games.module';
|
||||
import { UsersModule } from './users/users.module';
|
||||
import { CardsService } from './cards/cards.service';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
imports: [
|
||||
ConfigModule.forRoot(),
|
||||
MongooseModule.forRoot('mongodb://localhost:27017', {
|
||||
auth: { password: 'example', username: 'root' },
|
||||
dbName: 'heron',
|
||||
}),
|
||||
GamesModule,
|
||||
UsersModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
providers: [AppService, CardsService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { Card, CardSchema } from './schemas/cards.schema';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MongooseModule.forFeature([{ name: Card.name, schema: CardSchema }]),
|
||||
],
|
||||
exports: [MongooseModule],
|
||||
})
|
||||
export class CardsModule {
|
||||
schema = CardSchema;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { CardsService } from './cards.service';
|
||||
|
||||
describe('CardsService', () => {
|
||||
let service: CardsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [CardsService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<CardsService>(CardsService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
@Injectable()
|
||||
export class CardsService {
|
||||
async loadCards(): Promise<void> {
|
||||
const cardPath = path.join(path.resolve(), 'cards');
|
||||
const files = await fs.readdir(cardPath);
|
||||
const processed = await Promise.all(
|
||||
files.map(async (f) => {
|
||||
return [
|
||||
path.parse(f).name,
|
||||
(
|
||||
await fs
|
||||
.readFile(path.join(cardPath, f))
|
||||
.then((e) => JSON.parse(e.toString()))
|
||||
).cards,
|
||||
];
|
||||
}),
|
||||
);
|
||||
|
||||
const packs = Object.fromEntries(processed);
|
||||
console.log(packs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||
import { CardFaction, CardRole, CardType, EffectParser } from './cards.types';
|
||||
|
||||
@Schema()
|
||||
export class Card {
|
||||
cardId: number;
|
||||
role: CardRole;
|
||||
cardType: CardType;
|
||||
faction: CardFaction;
|
||||
name: string;
|
||||
initAmount: number;
|
||||
cost?: number;
|
||||
defense?: number;
|
||||
guard?: boolean;
|
||||
effects: EffectParser[];
|
||||
}
|
||||
|
||||
export const CardSchema = SchemaFactory.createForClass(Card);
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export enum CardRole {
|
||||
PERSONAL = 'personal',
|
||||
MARKET = 'market',
|
||||
FIRE_GEM = 'fire_gem',
|
||||
}
|
||||
|
||||
/**
|
||||
* Playable card types
|
||||
*/
|
||||
export enum CardType {
|
||||
HERO = 'hero',
|
||||
HERO_ABILITY = 'hero_ability',
|
||||
CURRENCY = 'currency',
|
||||
WEAPON = 'weapon',
|
||||
CHAMPION = 'champion',
|
||||
ACTION = 'action',
|
||||
ITEM = 'item',
|
||||
}
|
||||
|
||||
/**
|
||||
* Playable card factions
|
||||
*/
|
||||
export enum CardFaction {
|
||||
BASE = 'base',
|
||||
|
||||
BLUE = 'blue',
|
||||
RED = 'red',
|
||||
GREEN = 'green',
|
||||
YELLOW = 'yellow',
|
||||
}
|
||||
|
||||
export enum CardEffects {
|
||||
GOLD = 'gold',
|
||||
DAMAGE = 'damage',
|
||||
HEAL = 'heal',
|
||||
PREPARE = 'prepare',
|
||||
STUN = 'stun',
|
||||
SACRIFICE = 'sacrifice',
|
||||
DRAW = 'draw',
|
||||
DRAW_AND_DISCARD = 'draw_and_discard',
|
||||
MAKE_DISCARD = 'make_discard',
|
||||
RESTACK_DISCARDED_CHAMPION = 'restack_discarded_champion',
|
||||
RESTACK_DISCARDED_CARD = 'restack_discarded_card',
|
||||
STACK_NEXT_ACTION_BOUGHT = 'stack_next_action_bought',
|
||||
STACK_NEXT_CARD_BOUGHT = 'stack_next_card_bought',
|
||||
PLAY_NEXT_CARD_BOUGHT = 'play_next_card_bought',
|
||||
}
|
||||
|
||||
export enum Per {
|
||||
CHAMPION = 'champion',
|
||||
OTHER_CHAMPION = 'other_champion',
|
||||
OTHER_GUARD = 'other_guard',
|
||||
CARD_OF_SAME_FACTION = 'card_of_same_faction',
|
||||
OTHER_CARD_OF_SAME_FACTION = 'other_card_of_same_faction',
|
||||
}
|
||||
|
||||
export enum EffectType {
|
||||
CHAMPION_ACTION = 'champion_action',
|
||||
SUICIDE = 'suicide',
|
||||
FACTION_COMBO = 'faction_combo',
|
||||
}
|
||||
|
||||
export enum EffectTypeSpecial {
|
||||
CHAMPION_AMOUNT = 'champion_amount',
|
||||
TOTAL_DAMAGE = 'total_damage',
|
||||
CARD_AMOUNT = 'card_amount',
|
||||
ACTION_AMOUNT = 'action_amount',
|
||||
}
|
||||
|
||||
export type EffectTypeVerbose = {
|
||||
id: EffectType | EffectTypeSpecial;
|
||||
faction?: string;
|
||||
amount?: number;
|
||||
card_id?: number;
|
||||
};
|
||||
|
||||
export type EffectParser = {
|
||||
effect: CardEffects;
|
||||
amount: number;
|
||||
times?: number;
|
||||
sub_effects?: EffectParser[];
|
||||
effect_type?: EffectType | EffectTypeVerbose;
|
||||
per?: Per;
|
||||
mutex?: number;
|
||||
};
|
||||
|
||||
export type CardParser = {
|
||||
id: number;
|
||||
role: CardRole;
|
||||
card_type: CardType;
|
||||
faction: CardFaction;
|
||||
name: string;
|
||||
init_amount: number;
|
||||
cost?: number;
|
||||
amount: number;
|
||||
defense?: number;
|
||||
guard?: boolean;
|
||||
effects: EffectParser[];
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export class CreateGameDto {}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateGameDto } from './create-game.dto';
|
||||
|
||||
export class UpdateGameDto extends PartialType(CreateGameDto) {}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { GamesController } from './games.controller';
|
||||
import { GamesService } from './games.service';
|
||||
|
||||
describe('GamesController', () => {
|
||||
let controller: GamesController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [GamesController],
|
||||
providers: [GamesService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<GamesController>(GamesController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { GamesService } from './games.service';
|
||||
import { CreateGameDto } from './dto/create-game.dto';
|
||||
import { UpdateGameDto } from './dto/update-game.dto';
|
||||
|
||||
@Controller('games')
|
||||
export class GamesController {
|
||||
constructor(private readonly gamesService: GamesService) {}
|
||||
|
||||
@Post()
|
||||
create() {
|
||||
return this.gamesService.create();
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.gamesService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.gamesService.findOne(id);
|
||||
}
|
||||
|
||||
// @Patch(':id')
|
||||
// update(@Param('id') id: string, @Body() updateGameDto: UpdateGameDto) {
|
||||
// return this.gamesService.update(+id, updateGameDto);
|
||||
// }
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.gamesService.remove(+id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { GamesService } from './games.service';
|
||||
import { GamesController } from './games.controller';
|
||||
import { Game, GameSchema } from './schemas/game.schema';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { UsersModule } from 'src/users/users.module';
|
||||
import { CardsModule } from 'src/cards/cards.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CardsModule,
|
||||
UsersModule,
|
||||
MongooseModule.forFeature([{ name: Game.name, schema: GameSchema }]),
|
||||
],
|
||||
controllers: [GamesController],
|
||||
providers: [GamesService],
|
||||
})
|
||||
export class GamesModule {}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { GamesService } from './games.service';
|
||||
|
||||
describe('GamesService', () => {
|
||||
let service: GamesService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [GamesService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<GamesService>(GamesService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateGameDto } from './dto/create-game.dto';
|
||||
import { UpdateGameDto } from './dto/update-game.dto';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Game } from './schemas/game.schema';
|
||||
import { Model } from 'mongoose';
|
||||
|
||||
@Injectable()
|
||||
export class GamesService {
|
||||
constructor(@InjectModel(Game.name) private gameModel: Model<Game>) {}
|
||||
async create(): Promise<Game> {
|
||||
const createdGame = new this.gameModel();
|
||||
return createdGame.save();
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return this.gameModel.find();
|
||||
}
|
||||
|
||||
findOne(id: string) {
|
||||
return this.gameModel.find({ _id: id }).exec();
|
||||
}
|
||||
|
||||
// update(id: number, updateGameDto: UpdateGameDto) {
|
||||
// return `This action updates a #${id} game`;
|
||||
// }
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} game`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||
import mongoose, { HydratedDocument } from 'mongoose';
|
||||
import { Card } from 'src/cards/schemas/cards.schema';
|
||||
import { User } from 'src/users/schemas/user.entity';
|
||||
|
||||
export type GameDocument = HydratedDocument<Game>;
|
||||
|
||||
@Schema()
|
||||
class GameObject {}
|
||||
|
||||
@Schema()
|
||||
export class Team extends GameObject {}
|
||||
|
||||
export const TeamSchema = SchemaFactory.createForClass(Team);
|
||||
|
||||
@Schema()
|
||||
export class Container extends GameObject {
|
||||
@Prop([{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }])
|
||||
cards: Card[];
|
||||
}
|
||||
|
||||
export const ContainerSchema = SchemaFactory.createForClass(Container);
|
||||
|
||||
@Schema()
|
||||
export class Game extends GameObject {
|
||||
@Prop([Team])
|
||||
teams: Team[];
|
||||
|
||||
@Prop({ type: ContainerSchema, default: {} })
|
||||
market: Container;
|
||||
|
||||
@Prop({ type: ContainerSchema, default: {} })
|
||||
marketStack: Container;
|
||||
|
||||
@Prop({ type: ContainerSchema, default: {} })
|
||||
fireGems: Container;
|
||||
|
||||
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Game.teams' })
|
||||
currentTurn?: Team;
|
||||
|
||||
@Prop({ default: false })
|
||||
started: boolean;
|
||||
|
||||
@Prop({ default: false })
|
||||
private ended: boolean;
|
||||
|
||||
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' })
|
||||
owner: User;
|
||||
}
|
||||
|
||||
export const GameSchema = SchemaFactory.createForClass(Game);
|
||||
+21
-1
@@ -1,8 +1,28 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import * as session from 'express-session';
|
||||
import { AppModule } from './app.module';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
await app.listen(3000);
|
||||
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('Heron Game')
|
||||
.setDescription('Heron')
|
||||
.setVersion('1.0')
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('api', app, document);
|
||||
|
||||
app.use(
|
||||
session({
|
||||
secret: 'my-secret',
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
name: 'heron.session',
|
||||
}),
|
||||
);
|
||||
|
||||
await app.listen(8765);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export class CreateUserDto {
|
||||
method: 'discord' | 'password';
|
||||
username?: string;
|
||||
password: string;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateUserDto } from './create-user.dto';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class LoginDto extends PartialType(CreateUserDto) {
|
||||
constructor(id: string, global_name: string, avatar: string) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.global_name = global_name;
|
||||
this.avatar = avatar;
|
||||
}
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
|
||||
@ApiProperty()
|
||||
global_name: string;
|
||||
|
||||
@ApiProperty()
|
||||
avatar: string;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||
import { HydratedDocument } from 'mongoose';
|
||||
|
||||
export type UserDocument = HydratedDocument<User>;
|
||||
|
||||
@Schema()
|
||||
export class User {}
|
||||
|
||||
export const UserSchema = SchemaFactory.createForClass(User);
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UsersController } from './users.controller';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
describe('UsersController', () => {
|
||||
let controller: UsersController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [UsersController],
|
||||
providers: [UsersService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<UsersController>(UsersController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Query,
|
||||
Session,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { UsersService } from './users.service';
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('auth')
|
||||
@Controller()
|
||||
export class UsersController {
|
||||
constructor(private readonly usersService: UsersService) {}
|
||||
|
||||
@Get('/login')
|
||||
async login(
|
||||
@Query('code') code: string,
|
||||
@Session() session: Record<string, any>,
|
||||
) {
|
||||
const tokenData = await this.usersService.saveDiscordToken(code);
|
||||
session.tokenData = tokenData;
|
||||
const user = await this.usersService.useDiscordToken(
|
||||
tokenData.token_type,
|
||||
tokenData.token,
|
||||
);
|
||||
return user;
|
||||
}
|
||||
|
||||
@Get('/whoami')
|
||||
async remove(@Session() session: Record<string, any>): Promise<LoginDto> {
|
||||
if (!session.tokenData) {
|
||||
throw new HttpException(
|
||||
"Session doesn't contains auth information",
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const user = await this.usersService.useDiscordToken(
|
||||
session.tokenData.token_type,
|
||||
session.tokenData.token,
|
||||
);
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UsersService } from './users.service';
|
||||
import { UsersController } from './users.controller';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { User, UserSchema } from './schemas/user.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
|
||||
],
|
||||
controllers: [UsersController],
|
||||
providers: [UsersService],
|
||||
exports: [MongooseModule],
|
||||
})
|
||||
export class UsersModule {
|
||||
schema = UserSchema;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
describe('UsersService', () => {
|
||||
let service: UsersService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [UsersService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<UsersService>(UsersService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
import { URLSearchParams } from 'url';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
async saveDiscordToken(code: string) {
|
||||
const req = await fetch('https://discord.com/api/oauth2/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
client_id: process.env.DISCORD_CLIENTID,
|
||||
client_secret: process.env.DISCORD_CLIENTSECRET,
|
||||
code: code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: process.env.API_ENDPOINT + '/login',
|
||||
scope: 'identify',
|
||||
}),
|
||||
});
|
||||
if (!req.ok) {
|
||||
console.error(await req.json());
|
||||
throw new HttpException(
|
||||
"Couldn't fetch Discord api",
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
const data = await req.json();
|
||||
if (!data.token_type || !data.access_token) {
|
||||
console.error(data);
|
||||
throw new HttpException(
|
||||
'Invalid Discord API response',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
const token_type = data.token_type;
|
||||
const token = data.access_token;
|
||||
|
||||
return { token, token_type };
|
||||
}
|
||||
|
||||
async useDiscordToken(token_type: string, token: string) {
|
||||
const req2 = await fetch('https://discord.com/api/users/@me', {
|
||||
headers: {
|
||||
authorization: token_type + ' ' + token,
|
||||
},
|
||||
});
|
||||
|
||||
if (!req2.ok) {
|
||||
throw new HttpException(
|
||||
"Couldn't fetch Discord api",
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
const data2 = await req2.json();
|
||||
return new LoginDto(data2.id, data2.global_name, data2.avatar);
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} user`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user