From bb4d0970df059794dca0bdf3e330e0d53b1dea08 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 14:30:42 +0900 Subject: [PATCH 01/20] [fix] games table column name --- docs/dbdiagram | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dbdiagram b/docs/dbdiagram index 086c564..a47541c 100644 --- a/docs/dbdiagram +++ b/docs/dbdiagram @@ -11,7 +11,7 @@ Table players { Table games { game_id INTEGER [pk, increment] - count INTEGER [not null] + entry_count INTEGER [not null] stack INTEGER [not null] date VARCHAR(10) [not null, note: "format: \"2022-01-23\""] } From 4f237189b6bd170966764ddb5b86c2471f70145b Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 14:46:45 +0900 Subject: [PATCH 02/20] [add] game and calculate type --- src/usecase/types/calculate.ts | 20 ++++++++++++++++++++ src/usecase/types/game.ts | 12 ++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/usecase/types/calculate.ts create mode 100644 src/usecase/types/game.ts diff --git a/src/usecase/types/calculate.ts b/src/usecase/types/calculate.ts new file mode 100644 index 0000000..a6d715b --- /dev/null +++ b/src/usecase/types/calculate.ts @@ -0,0 +1,20 @@ +export type Calculate = { + calc_id: number; + game_id: number; + winner_name: string; + winner_rate: number; + winner_is_excluded: boolean; + loser_name: string; + loser_rate: number; + loser_is_excluded: boolean; +}; + +export type NewCalculate = { + game_id: number; + winner_name: string; + winner_rate: number; + winner_is_excluded: boolean; + loser_name: string; + loser_rate: number; + loser_is_excluded: boolean; +}; diff --git a/src/usecase/types/game.ts b/src/usecase/types/game.ts new file mode 100644 index 0000000..158165a --- /dev/null +++ b/src/usecase/types/game.ts @@ -0,0 +1,12 @@ +export type Game = { + gameId: number; + entryCount: number; + stack: number; + date: Date; +}; + +export type NewGame = { + entryCount: number; + stack: number; + date: Date; +}; From 0392791d18c273082d9703943dc800a8680206f0 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 15:13:00 +0900 Subject: [PATCH 03/20] [fix] calculate type key to camel case --- src/usecase/types/calculate.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/usecase/types/calculate.ts b/src/usecase/types/calculate.ts index a6d715b..33d7c52 100644 --- a/src/usecase/types/calculate.ts +++ b/src/usecase/types/calculate.ts @@ -1,20 +1,20 @@ export type Calculate = { - calc_id: number; - game_id: number; - winner_name: string; - winner_rate: number; - winner_is_excluded: boolean; - loser_name: string; - loser_rate: number; - loser_is_excluded: boolean; + calcId: number; + gameId: number; + winnerName: string; + winnerRate: number; + winnerIsExcluded: boolean; + loserName: string; + loserRate: number; + loserIsExcluded: boolean; }; export type NewCalculate = { - game_id: number; - winner_name: string; - winner_rate: number; - winner_is_excluded: boolean; - loser_name: string; - loser_rate: number; - loser_is_excluded: boolean; + gameId: number; + winnerName: string; + winnerRate: number; + winnerIsExcluded: boolean; + loserName: string; + loserRate: number; + loserIsExcluded: boolean; }; From bf0f26429e3779b03e0d3968cbdd25b94a62c98a Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 15:14:28 +0900 Subject: [PATCH 04/20] [add] response parser --- src/usecase/functions/parseJson.ts | 86 ++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/usecase/functions/parseJson.ts b/src/usecase/functions/parseJson.ts index b4c1903..ca3b80f 100644 --- a/src/usecase/functions/parseJson.ts +++ b/src/usecase/functions/parseJson.ts @@ -1,3 +1,5 @@ +import { Calculate } from '../types/calculate'; +import { Game } from '../types/game'; import { Player } from '../types/player'; export const parsePlayer = (rawData: unknown): Player => { @@ -49,3 +51,87 @@ export const parsePlayerList = (rawData: unknown[]): Player[] => { }); return playerList; }; + +export const parseGame = (rawData: unknown): Game => { + const data = rawData as { + game_id: number; + entry_count: number; + stack: number; + date: Date; + }; + return { + gameId: data.game_id, + entryCount: data.entry_count, + stack: data.stack, + date: data.date, + }; +}; + +export const parseGameList = (rawData: unknown[]): Game[] => { + const dataList = rawData as { + game_id: number; + entry_count: number; + stack: number; + date: Date; + }[]; + const gameList: Game[] = []; + dataList.forEach((data) => { + gameList.push({ + gameId: data.game_id, + entryCount: data.entry_count, + stack: data.stack, + date: data.date, + }); + }); + return gameList; +}; + +export const parseCalculate = (rawData: unknown): Calculate => { + const data = rawData as { + calc_id: number; + game_id: number; + winner_name: string; + winner_rate: number; + winner_is_excluded: boolean; + loser_name: string; + loser_rate: number; + loser_is_excluded: boolean; + }; + return { + calcId: data.calc_id, + gameId: data.game_id, + winnerName: data.winner_name, + winnerRate: data.winner_rate, + winnerIsExcluded: data.winner_is_excluded, + loserName: data.loser_name, + loserRate: data.loser_rate, + loserIsExcluded: data.loser_is_excluded, + }; +}; + +export const parseCalculateList = (rawData: unknown[]): Calculate[] => { + const dataList = rawData as { + calc_id: number; + game_id: number; + winner_name: string; + winner_rate: number; + winner_is_excluded: boolean; + loser_name: string; + loser_rate: number; + loser_is_excluded: boolean; + }[]; + const calculateList: Calculate[] = []; + dataList.forEach((data) => { + calculateList.push({ + calcId: data.calc_id, + gameId: data.game_id, + winnerName: data.winner_name, + winnerRate: data.winner_rate, + winnerIsExcluded: data.winner_is_excluded, + loserName: data.loser_name, + loserRate: data.loser_rate, + loserIsExcluded: data.loser_is_excluded, + }); + }); + return calculateList; +}; From 66c3a613171f09eac25db39233d6d0d9dc411475 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 15:29:11 +0900 Subject: [PATCH 05/20] [fix] remove \n in sql --- src/adapter/queries/player.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/adapter/queries/player.ts b/src/adapter/queries/player.ts index 28e1e80..a1cbc9f 100644 --- a/src/adapter/queries/player.ts +++ b/src/adapter/queries/player.ts @@ -83,9 +83,7 @@ export class PlayerController implements IController { async update(replaceData: Player): Promise { const result = await this.pool .query( - `UPDATE players SET discord_id = $2, current_rate = $3, max_rate = $4, - game_count = $5, first_win_count = $6, second_win_count = $7, third_win_count = $8 - WHERE player_name = $1 RETURNING *`, + `UPDATE players SET discord_id = $2, current_rate = $3, max_rate = $4, game_count = $5, first_win_count = $6, second_win_count = $7, third_win_count = $8 WHERE player_name = $1 RETURNING *`, [ replaceData.playerName, replaceData.discordId || '', From 8cdb90e65f05b9934d2498d128bb3b62021da645 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 15:29:31 +0900 Subject: [PATCH 06/20] [add] game and calculate controller --- src/adapter/queries/calculate.ts | 91 ++++++++++++++++++++++++++++++++ src/adapter/queries/game.ts | 78 +++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 src/adapter/queries/calculate.ts create mode 100644 src/adapter/queries/game.ts diff --git a/src/adapter/queries/calculate.ts b/src/adapter/queries/calculate.ts new file mode 100644 index 0000000..86caeda --- /dev/null +++ b/src/adapter/queries/calculate.ts @@ -0,0 +1,91 @@ +import { + parseCalculate, + parseCalculateList, +} from '../../usecase/functions/parseJson'; +import { IController } from '../../usecase/interfaces/controller'; +import { IDatabase } from '../../usecase/interfaces/db'; +import { Calculate, NewCalculate } from '../../usecase/types/calculate'; + +export class CalculateController + implements IController +{ + private pool!: IDatabase; + + setPool(pool: IDatabase): void { + this.pool = pool; + } + + async create(newData: NewCalculate): Promise { + const result = await this.pool + .query( + `INSERT INTO calculates (game_id, winner_name, winner_rate, winner_is_excluded, loser_name, loser_rate, loser_is_excluded) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`, + [ + newData.gameId.toString(), + newData.winnerName, + newData.winnerRate.toString(), + newData.winnerIsExcluded.toString(), + newData.loserName, + newData.loserRate.toString(), + newData.loserIsExcluded.toString(), + ] + ) + .catch((error) => { + throw error; + }); + return parseCalculate(result[0]); + } + + async read(id: string): Promise { + const result = await this.pool + .query(`SELECT * FROM calculates WHERE calc_id = $1`, [id]) + .catch((error) => { + throw error; + }); + if (result.length === 0) { + throw new Error(`Calculate Not Found: ${id}`); + } + return parseCalculate(result[0]); + } + + async readAll(): Promise { + const result = await this.pool + .query(`SELECT * FROM calculates`) + .catch((error) => { + throw error; + }); + if (result.length === 0) { + throw new Error(`Calculate Not Found`); + } + return parseCalculateList(result); + } + + async update(replaceData: Calculate): Promise { + const result = await this.pool + .query( + `UPDATE calculates SET game_id = $2, winner_name = $3, winner_rate = $4, winner_is_excluded = $5, loser_name = $6, loser_rate = $7, loser_is_excluded = $8 WHERE calc_id = $1 RETURNING *`, + [ + replaceData.calcId.toString(), + replaceData.gameId.toString(), + replaceData.winnerName, + replaceData.winnerRate.toString(), + replaceData.winnerIsExcluded.toString(), + replaceData.loserName, + replaceData.loserRate.toString(), + replaceData.loserIsExcluded.toString(), + ] + ) + .catch((error) => { + throw error; + }); + return parseCalculate(result[0]); + } + + async delete(id: string): Promise { + const result = await this.pool + .query(`DELETE FROM calculates WHERE calc_id = $1 RETURNING *`, [id]) + .catch((error) => { + throw error; + }); + return parseCalculate(result[0]); + } +} diff --git a/src/adapter/queries/game.ts b/src/adapter/queries/game.ts new file mode 100644 index 0000000..eb89f41 --- /dev/null +++ b/src/adapter/queries/game.ts @@ -0,0 +1,78 @@ +import { parseGame, parseGameList } from '../../usecase/functions/parseJson'; +import { IController } from '../../usecase/interfaces/controller'; +import { IDatabase } from '../../usecase/interfaces/db'; +import { Game, NewGame } from '../../usecase/types/game'; + +export class GameController implements IController { + private pool!: IDatabase; + + setPool(pool: IDatabase): void { + this.pool = pool; + } + + async create(newData: NewGame): Promise { + const result = await this.pool + .query( + `INSERT INTO games (entry_count, stack, date) VALUES ($1, $2, $3) RETURNING *`, + [ + newData.entryCount.toString(), + newData.stack.toString(), + newData.date.toString(), + ] + ) + .catch((error) => { + throw error; + }); + return parseGame(result[0]); + } + + async read(id: string): Promise { + const result = await this.pool + .query(`SELECT * FROM games WHERE game_id = $1`, [id]) + .catch((error) => { + throw error; + }); + if (result.length === 0) { + throw new Error(`Game Not Found: ${id}`); + } + return parseGame(result[0]); + } + + async readAll(): Promise { + const result = await this.pool + .query(`SELECT * FROM games`) + .catch((error) => { + throw error; + }); + if (result.length === 0) { + throw new Error(`Game Not Found`); + } + return parseGameList(result); + } + + async update(replaceData: Game): Promise { + const result = await this.pool + .query( + `UPDATE games SET entry_count = $2, stack = $3, date = $4 WHERE game_id = $1 RETURNING *`, + [ + replaceData.gameId.toString(), + replaceData.entryCount.toString(), + replaceData.stack.toString(), + replaceData.date.toString(), + ] + ) + .catch((error) => { + throw error; + }); + return parseGame(result[0]); + } + + async delete(id: string): Promise { + const result = await this.pool + .query(`DELETE FROM games WHERE game_id = $1 RETURNING *`, [id]) + .catch((error) => { + throw error; + }); + return parseGame(result[0]); + } +} From 9e6a4ab891f284560d6981896895e5ca2ce22bf9 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 15:54:33 +0900 Subject: [PATCH 07/20] [add] set pool to new controllers --- src/adapter/queries/calculate.ts | 2 ++ src/adapter/queries/game.ts | 2 ++ src/main.ts | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/src/adapter/queries/calculate.ts b/src/adapter/queries/calculate.ts index 86caeda..afbe7cf 100644 --- a/src/adapter/queries/calculate.ts +++ b/src/adapter/queries/calculate.ts @@ -89,3 +89,5 @@ export class CalculateController return parseCalculate(result[0]); } } + +export const calculateController = new CalculateController(); diff --git a/src/adapter/queries/game.ts b/src/adapter/queries/game.ts index eb89f41..625f879 100644 --- a/src/adapter/queries/game.ts +++ b/src/adapter/queries/game.ts @@ -76,3 +76,5 @@ export class GameController implements IController { return parseGame(result[0]); } } + +export const gameController = new GameController(); diff --git a/src/main.ts b/src/main.ts index b848eaf..b507901 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,7 @@ import dotenv from 'dotenv'; import express from 'express'; +import { calculateController } from './adapter/queries/calculate'; +import { gameController } from './adapter/queries/game'; import { playerController } from './adapter/queries/player'; import { config } from './config/config'; import { runDiscordBot } from './infrastructure/discord'; @@ -11,6 +13,8 @@ config.load(); const pool = new PgPool(); pool.connect(); playerController.setPool(pool); +gameController.setPool(pool); +calculateController.setPool(pool); const app = express(); const PORT = process.env.PORT || 8080; From d9b04f960e9da3cc3df71c387a7b02869cd01626 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 20:41:00 +0900 Subject: [PATCH 08/20] [wip] result command validation --- package.json | 1 + pnpm-lock.yaml | 21 +++++++++++++++++++++ src/adapter/commands/result.ts | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index da7fa96..d1a9709 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@types/pg": "^8.10.9", + "date-fns": "^2.30.0", "discord.js": "^14.14.1", "dotenv": "^16.3.1", "express": "^4.18.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7f9142a..d11a565 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ dependencies: '@types/pg': specifier: ^8.10.9 version: 8.10.9 + date-fns: + specifier: ^2.30.0 + version: 2.30.0 discord.js: specifier: ^14.14.1 version: 14.14.1 @@ -141,6 +144,13 @@ packages: '@babel/types': 7.17.0 dev: true + /@babel/runtime@7.23.6: + resolution: {integrity: sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.1 + dev: false + /@babel/template@7.22.15: resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} engines: {node: '>=6.9.0'} @@ -922,6 +932,13 @@ packages: which: 2.0.2 dev: true + /date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + dependencies: + '@babel/runtime': 7.23.6 + dev: false + /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -2023,6 +2040,10 @@ packages: unpipe: 1.0.0 dev: false + /regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + dev: false + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} diff --git a/src/adapter/commands/result.ts b/src/adapter/commands/result.ts index d5ff679..f2e75ec 100644 --- a/src/adapter/commands/result.ts +++ b/src/adapter/commands/result.ts @@ -1,7 +1,39 @@ +import { isValid } from 'date-fns'; +import { parse } from 'date-fns/fp'; import { Reply, ReplyType } from '../../usecase/types/reply'; +const parseDate = parse(new Date(), 'yyyy-MM-dd'); + export const commandResult = (args: string[]): Reply => { - console.log(args); + if (args.length < 8) { + return { + type: ReplyType.Error, + errorText: + 'Invalid Arguments: Date, EntryCount, Stack, Players...(4 or more)', + }; + } + const date = parseDate(args[1]); + const entryCount = parseInt(args[2]); + const stack = parseInt(args[3]); + const players = args.slice(4); + if (!isValid(date) || date.getFullYear() < 2000) { + return { + type: ReplyType.Error, + errorText: 'Error: Invalid date format: YYYY-MM-DD', + }; + } + if (isNaN(entryCount) || isNaN(stack)) { + return { + type: ReplyType.Error, + errorText: 'Error: entry count and stack must be numbers', + }; + } + if (entryCount != players.length) { + return { + type: ReplyType.Error, + errorText: 'Error: entry count and number of players do not match', + }; + } return { type: ReplyType.Error, errorText: 'Error: Not Implemented', From d04a50775178809602223e3ef7ee465a5650f770 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 20:59:51 +0900 Subject: [PATCH 09/20] [wip] add players validation --- src/adapter/commands/result.ts | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/adapter/commands/result.ts b/src/adapter/commands/result.ts index f2e75ec..0d6118f 100644 --- a/src/adapter/commands/result.ts +++ b/src/adapter/commands/result.ts @@ -1,10 +1,12 @@ import { isValid } from 'date-fns'; import { parse } from 'date-fns/fp'; +import { Player } from '../../usecase/types/player'; import { Reply, ReplyType } from '../../usecase/types/reply'; +import { playerController } from '../queries/player'; const parseDate = parse(new Date(), 'yyyy-MM-dd'); -export const commandResult = (args: string[]): Reply => { +export const commandResult = async (args: string[]): Promise => { if (args.length < 8) { return { type: ReplyType.Error, @@ -12,10 +14,14 @@ export const commandResult = (args: string[]): Reply => { 'Invalid Arguments: Date, EntryCount, Stack, Players...(4 or more)', }; } + const date = parseDate(args[1]); const entryCount = parseInt(args[2]); const stack = parseInt(args[3]); - const players = args.slice(4); + const playerNameList = args.slice(4); + const playerList: Player[] = []; + + // validation if (!isValid(date) || date.getFullYear() < 2000) { return { type: ReplyType.Error, @@ -28,12 +34,28 @@ export const commandResult = (args: string[]): Reply => { errorText: 'Error: entry count and stack must be numbers', }; } - if (entryCount != players.length) { + if (entryCount != playerNameList.length) { return { type: ReplyType.Error, errorText: 'Error: entry count and number of players do not match', }; } + for (let i = 0; i < playerNameList.length; i++) { + try { + const player = await playerController.readByDiscordOrName( + playerNameList[i] + ); + playerList.push(player); + } catch (error) { + return { + type: ReplyType.Error, + errorText: `${error}`, + }; + } + } + + // insert data + return { type: ReplyType.Error, errorText: 'Error: Not Implemented', From 85a195b87ca01d5260c27642a066bb76996be121 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 21:30:43 +0900 Subject: [PATCH 10/20] [chore] change to save date as string --- src/usecase/functions/parseJson.ts | 4 ++-- src/usecase/types/game.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/usecase/functions/parseJson.ts b/src/usecase/functions/parseJson.ts index ca3b80f..d25fee9 100644 --- a/src/usecase/functions/parseJson.ts +++ b/src/usecase/functions/parseJson.ts @@ -57,7 +57,7 @@ export const parseGame = (rawData: unknown): Game => { game_id: number; entry_count: number; stack: number; - date: Date; + date: string; }; return { gameId: data.game_id, @@ -72,7 +72,7 @@ export const parseGameList = (rawData: unknown[]): Game[] => { game_id: number; entry_count: number; stack: number; - date: Date; + date: string; }[]; const gameList: Game[] = []; dataList.forEach((data) => { diff --git a/src/usecase/types/game.ts b/src/usecase/types/game.ts index 158165a..278b57b 100644 --- a/src/usecase/types/game.ts +++ b/src/usecase/types/game.ts @@ -2,11 +2,11 @@ export type Game = { gameId: number; entryCount: number; stack: number; - date: Date; + date: string; }; export type NewGame = { entryCount: number; stack: number; - date: Date; + date: string; }; From 05e2c0900b5689b7a9028ea09777f787f3a72da0 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 21:50:48 +0900 Subject: [PATCH 11/20] [add] transaction controller --- src/adapter/queries/transaction.ts | 29 +++++++++++++++++++++++++++++ src/main.ts | 2 ++ 2 files changed, 31 insertions(+) create mode 100644 src/adapter/queries/transaction.ts diff --git a/src/adapter/queries/transaction.ts b/src/adapter/queries/transaction.ts new file mode 100644 index 0000000..85deff7 --- /dev/null +++ b/src/adapter/queries/transaction.ts @@ -0,0 +1,29 @@ +import { IDatabase } from '../../usecase/interfaces/db'; + +export class TransactionController { + private pool!: IDatabase; + + setPool(pool: IDatabase): void { + this.pool = pool; + } + + async begin(): Promise { + await this.pool.query(`BEGIN TRANSACTION`).catch((error) => { + throw error; + }); + } + + async commit(): Promise { + await this.pool.query(`COMMIT TRANSACTION`).catch((error) => { + throw error; + }); + } + + async rollback(): Promise { + await this.pool.query(`ROLLBACK TRANSACTION`).catch((error) => { + throw error; + }); + } +} + +export const transactionController = new TransactionController(); diff --git a/src/main.ts b/src/main.ts index b507901..0908aa3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,7 @@ import express from 'express'; import { calculateController } from './adapter/queries/calculate'; import { gameController } from './adapter/queries/game'; import { playerController } from './adapter/queries/player'; +import { transactionController } from './adapter/queries/transaction'; import { config } from './config/config'; import { runDiscordBot } from './infrastructure/discord'; import { PgPool } from './infrastructure/postgres'; @@ -12,6 +13,7 @@ config.load(); const pool = new PgPool(); pool.connect(); +transactionController.setPool(pool); playerController.setPool(pool); gameController.setPool(pool); calculateController.setPool(pool); From 1fef4b534d1faf4f2f9ba061e7d1bb120bd61f80 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 21:52:07 +0900 Subject: [PATCH 12/20] [wip] insert game and calc data --- src/adapter/commands/result.ts | 80 ++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/src/adapter/commands/result.ts b/src/adapter/commands/result.ts index 0d6118f..cebcf48 100644 --- a/src/adapter/commands/result.ts +++ b/src/adapter/commands/result.ts @@ -1,11 +1,46 @@ import { isValid } from 'date-fns'; import { parse } from 'date-fns/fp'; +import { config } from '../../config/config'; +import { Calculate, NewCalculate } from '../../usecase/types/calculate'; +import { Game, NewGame } from '../../usecase/types/game'; import { Player } from '../../usecase/types/player'; import { Reply, ReplyType } from '../../usecase/types/reply'; +import { calculateController } from '../queries/calculate'; +import { gameController } from '../queries/game'; import { playerController } from '../queries/player'; +import { transactionController } from '../queries/transaction'; const parseDate = parse(new Date(), 'yyyy-MM-dd'); +const insertGame = async (newData: NewGame): Promise => { + return await gameController + .create(newData) + .then((game) => { + return game; + }) + .catch((error) => { + throw error; + }); +}; + +const insertCalc = async (newData: NewCalculate): Promise => { + return await calculateController + .create(newData) + .then((game) => { + return game; + }) + .catch((error) => { + throw error; + }); +}; + +const isExcluded = (gameId: number, playerGameCount: number): boolean => { + const calculatingGameCount = config.calculatingGameCount; + if (gameId <= 5) return false; + if (playerGameCount <= calculatingGameCount) return true; + return false; +}; + export const commandResult = async (args: string[]): Promise => { if (args.length < 8) { return { @@ -15,14 +50,20 @@ export const commandResult = async (args: string[]): Promise => { }; } - const date = parseDate(args[1]); + const date = args[1]; + const parsedDate = parseDate(date); const entryCount = parseInt(args[2]); const stack = parseInt(args[3]); const playerNameList = args.slice(4); const playerList: Player[] = []; + const calcList: Calculate[] = []; // validation - if (!isValid(date) || date.getFullYear() < 2000) { + if ( + !isValid(parsedDate) || + date.length !== 10 || + parsedDate.getFullYear() < 2000 + ) { return { type: ReplyType.Error, errorText: 'Error: Invalid date format: YYYY-MM-DD', @@ -55,9 +96,42 @@ export const commandResult = async (args: string[]): Promise => { } // insert data + try { + await transactionController.begin(); + const game = await insertGame({ + date, + entryCount, + stack, + }); + for (let i = 0; i < playerList.length - 1; i++) { + for (let j = 1; j < playerList.length; j++) { + if (i < j) { + const calc = await insertCalc({ + gameId: game.gameId, + winnerName: playerList[i].playerName, + winnerRate: playerList[i].currentRate, + winnerIsExcluded: isExcluded(game.gameId, playerList[i].gameCount), + loserName: playerList[j].playerName, + loserRate: playerList[j].currentRate, + loserIsExcluded: isExcluded(game.gameId, playerList[j].gameCount), + }); + calcList.push(calc); + } + } + } + await transactionController.commit(); + } catch (error) { + await transactionController.rollback(); + return { + type: ReplyType.Error, + errorText: `${error}`, + }; + } + + // calculate rate return { type: ReplyType.Error, - errorText: 'Error: Not Implemented', + contentText: 'Not Implemented', }; }; From 7fa17c3147842742bfd3e9a27b2df6881c3b3997 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 21:55:51 +0900 Subject: [PATCH 13/20] [add] weighted coefficiant config --- application-settings.json | 3 ++- src/config/config.ts | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/application-settings.json b/application-settings.json index b7a11ce..e98948d 100644 --- a/application-settings.json +++ b/application-settings.json @@ -2,6 +2,7 @@ "commandPrefix": "./", "permittedRole": "admin", "initialRate": 1500, - "weightedCoefficient": 4, + "BaseWeightedCoefficient": 4, + "StackWeightedCoefficient": 1.5, "calculatingGameCount": 3 } \ No newline at end of file diff --git a/src/config/config.ts b/src/config/config.ts index ee957a1..d07434d 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -4,14 +4,16 @@ export class Config { commandPrefix: string; permittedRole: string; initialRate: number; - weightedCoefficient: number; + BaseWeightedCoefficient: number; + StackWeightedCoefficient: number; calculatingGameCount: number; constructor() { this.commandPrefix = './'; this.permittedRole = 'admin'; this.initialRate = 1500; - this.weightedCoefficient = 4; + this.BaseWeightedCoefficient = 4; + this.StackWeightedCoefficient = 1.5; this.calculatingGameCount = 3; } @@ -19,7 +21,8 @@ export class Config { this.commandPrefix = settings.commandPrefix; this.permittedRole = settings.permittedRole; this.initialRate = settings.initialRate; - this.weightedCoefficient = settings.weightedCoefficient; + this.BaseWeightedCoefficient = settings.BaseWeightedCoefficient; + this.StackWeightedCoefficient = settings.StackWeightedCoefficient; this.calculatingGameCount = settings.calculatingGameCount; console.log('Config Loaded Successfully'); } From 778a0504932347f2c06f1e9ee48cba2a1c02ef14 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 22:13:40 +0900 Subject: [PATCH 14/20] [chore] add stack column to calculates table --- docs/dbdiagram | 1 + src/adapter/commands/result.ts | 1 + src/cmd/migration.ts | 1 + src/usecase/functions/parseJson.ts | 4 ++++ src/usecase/types/calculate.ts | 2 ++ 5 files changed, 9 insertions(+) diff --git a/docs/dbdiagram b/docs/dbdiagram index a47541c..148ef2c 100644 --- a/docs/dbdiagram +++ b/docs/dbdiagram @@ -19,6 +19,7 @@ Table games { Table calculates { calc_id INTEGER [pk, increment] game_id INTEGER [not null, ref: > games.game_id] + stack INTEGER [not null] winner_name VARCHAR(20) [not null, ref: > players.player_name] winner_rate INTEGER [not null, note: "ref: players.current_rate"] winner_is_excluded BOOLEAN [not null, note: "default: (players.game_count <= ${CALUCRATING_GAME_COUNT})"] diff --git a/src/adapter/commands/result.ts b/src/adapter/commands/result.ts index cebcf48..b270bec 100644 --- a/src/adapter/commands/result.ts +++ b/src/adapter/commands/result.ts @@ -108,6 +108,7 @@ export const commandResult = async (args: string[]): Promise => { if (i < j) { const calc = await insertCalc({ gameId: game.gameId, + stack: game.stack, winnerName: playerList[i].playerName, winnerRate: playerList[i].currentRate, winnerIsExcluded: isExcluded(game.gameId, playerList[i].gameCount), diff --git a/src/cmd/migration.ts b/src/cmd/migration.ts index ac3658a..81cc33b 100644 --- a/src/cmd/migration.ts +++ b/src/cmd/migration.ts @@ -32,6 +32,7 @@ CREATE TABLE "games" ( CREATE TABLE "calculates" ( "calc_id" INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, "game_id" INTEGER NOT NULL, + "stack" INTEGER NOT NULL, "winner_name" VARCHAR(20) NOT NULL, "winner_rate" INTEGER NOT NULL, "winner_is_excluded" BOOLEAN NOT NULL, diff --git a/src/usecase/functions/parseJson.ts b/src/usecase/functions/parseJson.ts index d25fee9..1031627 100644 --- a/src/usecase/functions/parseJson.ts +++ b/src/usecase/functions/parseJson.ts @@ -90,6 +90,7 @@ export const parseCalculate = (rawData: unknown): Calculate => { const data = rawData as { calc_id: number; game_id: number; + stack: number; winner_name: string; winner_rate: number; winner_is_excluded: boolean; @@ -100,6 +101,7 @@ export const parseCalculate = (rawData: unknown): Calculate => { return { calcId: data.calc_id, gameId: data.game_id, + stack: data.stack, winnerName: data.winner_name, winnerRate: data.winner_rate, winnerIsExcluded: data.winner_is_excluded, @@ -113,6 +115,7 @@ export const parseCalculateList = (rawData: unknown[]): Calculate[] => { const dataList = rawData as { calc_id: number; game_id: number; + stack: number; winner_name: string; winner_rate: number; winner_is_excluded: boolean; @@ -125,6 +128,7 @@ export const parseCalculateList = (rawData: unknown[]): Calculate[] => { calculateList.push({ calcId: data.calc_id, gameId: data.game_id, + stack: data.stack, winnerName: data.winner_name, winnerRate: data.winner_rate, winnerIsExcluded: data.winner_is_excluded, diff --git a/src/usecase/types/calculate.ts b/src/usecase/types/calculate.ts index 33d7c52..4cf96ff 100644 --- a/src/usecase/types/calculate.ts +++ b/src/usecase/types/calculate.ts @@ -1,6 +1,7 @@ export type Calculate = { calcId: number; gameId: number; + stack: number; winnerName: string; winnerRate: number; winnerIsExcluded: boolean; @@ -11,6 +12,7 @@ export type Calculate = { export type NewCalculate = { gameId: number; + stack: number; winnerName: string; winnerRate: number; winnerIsExcluded: boolean; From d3b229defbd2e34d1c3ad694765d64d2ef9c5c1c Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 22:45:33 +0900 Subject: [PATCH 15/20] [fix] games table column name --- src/cmd/migration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/migration.ts b/src/cmd/migration.ts index 81cc33b..7234674 100644 --- a/src/cmd/migration.ts +++ b/src/cmd/migration.ts @@ -24,7 +24,7 @@ CREATE TABLE "players" ( CREATE TABLE "games" ( "game_id" INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "count" INTEGER NOT NULL, + "entry_count" INTEGER NOT NULL, "stack" INTEGER NOT NULL, "date" VARCHAR(10) NOT NULL ); From e9cdddd027cac0dad0affb1dadc7dfa04247bea8 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 22:46:50 +0900 Subject: [PATCH 16/20] [fix] controller set stack column in calculates table --- src/adapter/queries/calculate.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/adapter/queries/calculate.ts b/src/adapter/queries/calculate.ts index afbe7cf..6436611 100644 --- a/src/adapter/queries/calculate.ts +++ b/src/adapter/queries/calculate.ts @@ -18,9 +18,10 @@ export class CalculateController async create(newData: NewCalculate): Promise { const result = await this.pool .query( - `INSERT INTO calculates (game_id, winner_name, winner_rate, winner_is_excluded, loser_name, loser_rate, loser_is_excluded) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`, + `INSERT INTO calculates (game_id, stack, winner_name, winner_rate, winner_is_excluded, loser_name, loser_rate, loser_is_excluded) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`, [ newData.gameId.toString(), + newData.stack.toString(), newData.winnerName, newData.winnerRate.toString(), newData.winnerIsExcluded.toString(), @@ -62,10 +63,11 @@ export class CalculateController async update(replaceData: Calculate): Promise { const result = await this.pool .query( - `UPDATE calculates SET game_id = $2, winner_name = $3, winner_rate = $4, winner_is_excluded = $5, loser_name = $6, loser_rate = $7, loser_is_excluded = $8 WHERE calc_id = $1 RETURNING *`, + `UPDATE calculates SET game_id = $2, stack = $3, winner_name = $4, winner_rate = $5, winner_is_excluded = $6, loser_name = $7, loser_rate = $8, loser_is_excluded = $9 WHERE calc_id = $1 RETURNING *`, [ replaceData.calcId.toString(), replaceData.gameId.toString(), + replaceData.stack.toString(), replaceData.winnerName, replaceData.winnerRate.toString(), replaceData.winnerIsExcluded.toString(), From 6ed0523ce408ebddf33f8a1a3421c692a0e7af87 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 23:09:03 +0900 Subject: [PATCH 17/20] [add] function to make player object --- src/usecase/functions/makeObject.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/usecase/functions/makeObject.ts diff --git a/src/usecase/functions/makeObject.ts b/src/usecase/functions/makeObject.ts new file mode 100644 index 0000000..ef442a2 --- /dev/null +++ b/src/usecase/functions/makeObject.ts @@ -0,0 +1,11 @@ +import { Player } from '../types/player'; + +export const makePlayerObject = ( + playerList: Player[] +): Record => { + const playerObj: Record = {}; + playerList.forEach((player) => { + playerObj[player.playerName] = player; + }); + return playerObj; +}; From 2de220378fbe0d972aed6272821248b4ba3d720f Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 23:09:23 +0900 Subject: [PATCH 18/20] [add] calculate rate --- src/adapter/commands/result.ts | 23 ++++++++++- src/usecase/functions/calculateRate.ts | 53 ++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/usecase/functions/calculateRate.ts diff --git a/src/adapter/commands/result.ts b/src/adapter/commands/result.ts index b270bec..b802af1 100644 --- a/src/adapter/commands/result.ts +++ b/src/adapter/commands/result.ts @@ -1,6 +1,8 @@ import { isValid } from 'date-fns'; import { parse } from 'date-fns/fp'; import { config } from '../../config/config'; +import { calculateRate } from '../../usecase/functions/calculateRate'; +import { makePlayerObject } from '../../usecase/functions/makeObject'; import { Calculate, NewCalculate } from '../../usecase/types/calculate'; import { Game, NewGame } from '../../usecase/types/game'; import { Player } from '../../usecase/types/player'; @@ -57,6 +59,7 @@ export const commandResult = async (args: string[]): Promise => { const playerNameList = args.slice(4); const playerList: Player[] = []; const calcList: Calculate[] = []; + let gameId = -1; // validation if ( @@ -103,6 +106,7 @@ export const commandResult = async (args: string[]): Promise => { entryCount, stack, }); + gameId = game.gameId; for (let i = 0; i < playerList.length - 1; i++) { for (let j = 1; j < playerList.length; j++) { if (i < j) { @@ -130,9 +134,24 @@ export const commandResult = async (args: string[]): Promise => { } // calculate rate + try { + await transactionController.begin(); + const playerObj = makePlayerObject(playerList); + const updatedPlayerObj = calculateRate(playerObj, calcList); + Object.values(updatedPlayerObj).forEach(async (player) => { + await playerController.update(player); + }); + await transactionController.commit(); + } catch (error) { + await transactionController.rollback(); + return { + type: ReplyType.Error, + errorText: `${error}`, + }; + } return { - type: ReplyType.Error, - contentText: 'Not Implemented', + type: ReplyType.Text, + contentText: `Result Saved: ${stack}/${entryCount}entry, Id: ${gameId}`, }; }; diff --git a/src/usecase/functions/calculateRate.ts b/src/usecase/functions/calculateRate.ts new file mode 100644 index 0000000..2361ca4 --- /dev/null +++ b/src/usecase/functions/calculateRate.ts @@ -0,0 +1,53 @@ +import { config } from '../../config/config'; +import { Calculate } from '../types/calculate'; +import { Player } from '../types/player'; + +const BaseWeightedCoefficient = config.BaseWeightedCoefficient; +const StackWeightedCoefficient = config.StackWeightedCoefficient; + +const log100 = (x: number): number => { + return Math.log(x) / Math.log(100); +}; + +const calculateDiff = (calc: Calculate): number => { + const baseDiff = + 1 / (1 + Math.pow(10, (calc.winnerRate - calc.loserRate) / 400)); + return ( + baseDiff * + BaseWeightedCoefficient * + Math.pow(log100(calc.stack), StackWeightedCoefficient) + ); +}; + +const roundRate = ( + playerObj: Record +): Record => { + Object.values(playerObj).forEach((player) => { + player.currentRate = Math.round(player.currentRate); + player.maxRate = Math.max(player.currentRate, player.maxRate); + player.gameCount++; + }); + return playerObj; +}; + +export const calculateRate = ( + playerObj: Record, + calcList: Calculate[] +): Record => { + let gameCount = calcList[0].gameId; + calcList.forEach((calc) => { + if (calc.gameId !== gameCount) { + playerObj = roundRate(playerObj); + gameCount = calc.gameId; + } + const diff = calculateDiff(calcList[0]); + if (!calc.winnerIsExcluded) { + playerObj[calc.winnerName].currentRate += diff; + } + if (!calc.loserIsExcluded) { + playerObj[calc.loserName].currentRate -= diff; + } + }); + playerObj = roundRate(playerObj); + return playerObj; +}; From fa847821179e5751c43864fdb75fbc6cb1be6ba6 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 23:18:57 +0900 Subject: [PATCH 19/20] [chore] define playerObj type --- src/usecase/functions/calculateRate.ts | 10 ++++------ src/usecase/functions/makeObject.ts | 8 +++----- src/usecase/types/player.ts | 2 ++ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/usecase/functions/calculateRate.ts b/src/usecase/functions/calculateRate.ts index 2361ca4..02020c1 100644 --- a/src/usecase/functions/calculateRate.ts +++ b/src/usecase/functions/calculateRate.ts @@ -1,6 +1,6 @@ import { config } from '../../config/config'; import { Calculate } from '../types/calculate'; -import { Player } from '../types/player'; +import { PlayerObj } from '../types/player'; const BaseWeightedCoefficient = config.BaseWeightedCoefficient; const StackWeightedCoefficient = config.StackWeightedCoefficient; @@ -19,9 +19,7 @@ const calculateDiff = (calc: Calculate): number => { ); }; -const roundRate = ( - playerObj: Record -): Record => { +const roundRate = (playerObj: PlayerObj): PlayerObj => { Object.values(playerObj).forEach((player) => { player.currentRate = Math.round(player.currentRate); player.maxRate = Math.max(player.currentRate, player.maxRate); @@ -31,9 +29,9 @@ const roundRate = ( }; export const calculateRate = ( - playerObj: Record, + playerObj: PlayerObj, calcList: Calculate[] -): Record => { +): PlayerObj => { let gameCount = calcList[0].gameId; calcList.forEach((calc) => { if (calc.gameId !== gameCount) { diff --git a/src/usecase/functions/makeObject.ts b/src/usecase/functions/makeObject.ts index ef442a2..2fb4d85 100644 --- a/src/usecase/functions/makeObject.ts +++ b/src/usecase/functions/makeObject.ts @@ -1,9 +1,7 @@ -import { Player } from '../types/player'; +import { Player, PlayerObj } from '../types/player'; -export const makePlayerObject = ( - playerList: Player[] -): Record => { - const playerObj: Record = {}; +export const makePlayerObject = (playerList: Player[]): PlayerObj => { + const playerObj: PlayerObj = {}; playerList.forEach((player) => { playerObj[player.playerName] = player; }); diff --git a/src/usecase/types/player.ts b/src/usecase/types/player.ts index ed0e20c..bb51ba3 100644 --- a/src/usecase/types/player.ts +++ b/src/usecase/types/player.ts @@ -13,3 +13,5 @@ export type NewPlayer = { playerName: string; discordId?: string; }; + +export type PlayerObj = Record; From 2b8d7a2b06fbd3686028af8d060f6dced1d5fad0 Mon Sep 17 00:00:00 2001 From: claustra01 Date: Sun, 17 Dec 2023 23:19:12 +0900 Subject: [PATCH 20/20] [add] add medal --- src/adapter/commands/result.ts | 13 ++++++++++--- src/usecase/functions/addMedal.ts | 13 +++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 src/usecase/functions/addMedal.ts diff --git a/src/adapter/commands/result.ts b/src/adapter/commands/result.ts index b802af1..729bdac 100644 --- a/src/adapter/commands/result.ts +++ b/src/adapter/commands/result.ts @@ -1,6 +1,7 @@ import { isValid } from 'date-fns'; import { parse } from 'date-fns/fp'; import { config } from '../../config/config'; +import { addMedal } from '../../usecase/functions/addMedal'; import { calculateRate } from '../../usecase/functions/calculateRate'; import { makePlayerObject } from '../../usecase/functions/makeObject'; import { Calculate, NewCalculate } from '../../usecase/types/calculate'; @@ -137,8 +138,14 @@ export const commandResult = async (args: string[]): Promise => { try { await transactionController.begin(); const playerObj = makePlayerObject(playerList); - const updatedPlayerObj = calculateRate(playerObj, calcList); - Object.values(updatedPlayerObj).forEach(async (player) => { + const rateUpdatedPlayerObj = calculateRate(playerObj, calcList); + const medalUpdatedPlayerObj = addMedal( + rateUpdatedPlayerObj, + playerList[0].playerName, + playerList[1].playerName, + playerList[2].playerName + ); + Object.values(medalUpdatedPlayerObj).forEach(async (player) => { await playerController.update(player); }); await transactionController.commit(); @@ -146,7 +153,7 @@ export const commandResult = async (args: string[]): Promise => { await transactionController.rollback(); return { type: ReplyType.Error, - errorText: `${error}`, + errorText: 'Error: Calculation Failed: use `recalculate` command', }; } diff --git a/src/usecase/functions/addMedal.ts b/src/usecase/functions/addMedal.ts new file mode 100644 index 0000000..266e119 --- /dev/null +++ b/src/usecase/functions/addMedal.ts @@ -0,0 +1,13 @@ +import { PlayerObj } from '../types/player'; + +export const addMedal = ( + playerObj: PlayerObj, + firstWinner: string, + secondWinner: string, + thirdWinner: string +): PlayerObj => { + playerObj[firstWinner].firstWinCount++; + playerObj[secondWinner].secondWinCount++; + playerObj[thirdWinner].thirdWinCount++; + return playerObj; +};