Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#15 Feature/15 create gungi game #25

Merged
merged 5 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14,931 changes: 6,718 additions & 8,213 deletions server/package-lock.json

Large diffs are not rendered by default.

151 changes: 76 additions & 75 deletions server/package.json
Original file line number Diff line number Diff line change
@@ -1,75 +1,76 @@
{
"name": "server",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"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",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"lint-check": "eslint \"{src,apps,libs,test}/**/*.ts\"",
"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 --runInBand",
"test:unit": "jest --config ./test/unitTest/jest.config.js",
"test:unit:watch": "jest --config ./test/unitTest/jest.config.js --watch",
"test": "npm run test:unit && npm run test:e2e"
},
"dependencies": {
"@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.3.1",
"@nestjs/core": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"dotenv": "^16.0.3",
"mongodb": "^5.3.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@types/express": "^4.17.13",
"@types/jest": "29.2.4",
"@types/node": "18.11.18",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.22.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "29.3.1",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "29.0.3",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.1.1",
"typescript": "^4.7.4"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
{
"name": "server",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"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",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"lint-check": "eslint \"{src,apps,libs,test}/**/*.ts\"",
"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 --runInBand",
"test:unit": "jest --config ./test/unitTest/jest.config.js",
"test:unit:watch": "jest --config ./test/unitTest/jest.config.js --watch",
"test": "npm run test:unit && npm run test:e2e"
},
"dependencies": {
"@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.3.1",
"@nestjs/core": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"dotenv": "^16.0.3",
"mongodb": "^5.3.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@types/express": "^4.17.13",
"@types/jest": "29.2.4",
"@types/node": "18.11.18",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.22.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "29.3.1",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "29.0.3",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.1.1",
"typescript": "^4.7.4"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
8 changes: 8 additions & 0 deletions server/src/domain/events/CreateEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Event } from './Event';

export default interface CreateEvent extends Event {
name: 'CreateEvent';
data: {
gungiId: string;
};
}
17 changes: 17 additions & 0 deletions server/src/frameworks/filter/ZodFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
import { ZodError } from 'zod';

@Catch(ZodError)
export class ZodFilter<T extends ZodError> implements ExceptionFilter {
catch(exception: T, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const status = 400;

response.status(status).json({
statusCode: 400,
message: exception.message,
errors: exception.errors,
});
}
}
11 changes: 11 additions & 0 deletions server/src/frameworks/pipe/ZodPipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';

@Injectable()
export class ZodPipe implements PipeTransform {
constructor(private readonly schema: any) {}

transform(value: any, metadata: ArgumentMetadata) {
this.schema.parse(value);
return value;
}
}
22 changes: 22 additions & 0 deletions server/src/gateway/controllers/Gungi.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Body, Controller, HttpStatus, Param, Post, Res } from '@nestjs/common';
import CreateUsecase, {
CreateGungiRequest,
} from '../../usecases/service-class/CreateUsecase';
import FurigomaUsecase, {
FurigomaRequest,
} from '../../usecases/service-class/FurigomaUsecase';
Expand All @@ -19,16 +22,35 @@ import ConfigurationUsecase, {
ConfigurationRequest,
} from '../../usecases/service-class/ConfigurationUsecase';
import ConfigurationPresenter from '../presenter/ConfigurationPresenter';
import CreatePresenter from '../presenter/CreatePresenter';
import { ZodPipe } from '../../frameworks/pipe/ZodPipe';
import CreateGungiValidator from '../validation/create-body-validation';

@Controller()
export default class GungiController {
constructor(
private _createUsecase: CreateUsecase,
private _furigomaUsecase: FurigomaUsecase,
private _surrenderUsecase: SurrenderUsecase,
private _arataUsecase: ArataUsecase,
private _configurationUsecase: ConfigurationUsecase,
) {}

@Post('/gungi/create')
async create(
@Body(new ZodPipe(CreateGungiValidator))
body: { players: { id: string; nickname: string }[] },
@Res() res,
) {
const input: CreateGungiRequest = {
...body,
};
const presenter = new CreatePresenter();

const response = await this._createUsecase.present(input, presenter);
return res.status(HttpStatus.OK).send(response);
}

@Post('/gungi/:gungiId/furigoma')
async furigoma(
@Param('gungiId') gungiId: string,
Expand Down
17 changes: 17 additions & 0 deletions server/src/gateway/presenter/CreatePresenter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Presenter from '../../usecases/Presenter';
import { Event, findByEventName } from '../../domain/events/Event';

interface CreateGungiView {
url: string;
}

export default class CreatePresenter implements Presenter<CreateGungiView> {
present(events: Event[]): CreateGungiView {
const event = findByEventName(events, 'CreateEvent');
const data = event.data;
const gungiId = data.gungiId;
return {
url: `/gungi/${gungiId}`,
};
}
}
3 changes: 3 additions & 0 deletions server/src/gateway/requestBody/createGungiBody.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default class CreateGungiBody {
players: { id: string; nickname: string }[];
}
14 changes: 14 additions & 0 deletions server/src/gateway/validation/create-body-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { z } from 'zod';

const playerSchema = z.object({
id: z.string(),
nickname: z.string(),
});

const CreateGungiValidator = z.object({
players: z.array(playerSchema).refine((players) => players.length === 2, {
message: 'The "players" array must contain exactly 2 players.',
}),
});

export default CreateGungiValidator;
3 changes: 3 additions & 0 deletions server/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ZodFilter } from './frameworks/filter/ZodFilter';

const PORT = 8000;

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new ZodFilter());
await app.listen(PORT, () => {
console.log(`Server started on port ${PORT}`);
});
}

bootstrap();
3 changes: 3 additions & 0 deletions server/src/usecases/GungiUsecase.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import FurigomaUsecase from './service-class/FurigomaUsecase';
import ImplEventBus from '../gateway/eventBus/ImplEventBus';
import ArataUsecase from './service-class/ArataUsecase';
import ConfigurationUsecase from './service-class/ConfigurationUsecase';
import CreateUsecase from './service-class/CreateUsecase';

@Module({
imports: [GungiRepositoryModule],
providers: [
CreateUsecase,
SurrenderUsecase,
FurigomaUsecase,
ArataUsecase,
Expand All @@ -19,6 +21,7 @@ import ConfigurationUsecase from './service-class/ConfigurationUsecase';
},
],
exports: [
CreateUsecase,
SurrenderUsecase,
FurigomaUsecase,
ConfigurationUsecase,
Expand Down
57 changes: 57 additions & 0 deletions server/src/usecases/service-class/CreateUsecase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Usecase from '../Usecase';
import Presenter from '../Presenter';
import CreateEvent from '../../domain/events/CreateEvent';
import { Inject, Injectable } from '@nestjs/common';
import IRepository from '../Repository';
import Gungi from '../../domain/Gungi';
import EventBus from '../EventBus';
import { randomUUID } from 'crypto';
import LEVEL from '../../domain/constant/LEVEL';
import Player from '../../domain/Player';
import GungiHan from '../../domain/GungiHan';
import SIDE from '../../domain/constant/SIDE';
import DeadArea from '../../domain/DeadArea';
import GomaOki from '../../domain/GomaOki';

export type CreateGungiRequest = {
players: { id: string; nickname: string }[];
};

@Injectable()
export default class CreateUsecase implements Usecase<CreateGungiRequest> {
constructor(
@Inject('GungiRepository')
private _gungiRepository: IRepository<Gungi>,
@Inject('EventBus')
private _eventBus: EventBus,
) {}

async present<View>(
request: CreateGungiRequest,
presenter: Presenter<View>,
): Promise<View> {
const level = LEVEL.BEGINNER;
const gungi = new Gungi(
randomUUID(),
level,
request.players.map<Player>((player, index) => {
const side = index === 0 ? SIDE.WHITE : SIDE.BLACK;
const deadArea = new DeadArea(side, []);
const gomaOki = new GomaOki(level, side, []);
return new Player(player.id, player.nickname, side, gomaOki, deadArea);
}),
new GungiHan(level),
);
gungi.setCurrentTurn(SIDE.WHITE);

await this._gungiRepository.save(gungi);

const event: CreateEvent = {
name: 'CreateEvent',
data: {
gungiId: gungi.id,
},
};
return presenter.present([event]);
}
}
Loading
Loading