From 59c53a8417826cecd0f781b2575ebc5e8e4a9211 Mon Sep 17 00:00:00 2001 From: legonzaur Date: Sat, 12 Aug 2023 13:51:18 +0200 Subject: [PATCH] kill insertion working --- .vscode/launch.json | 20 +----- .vscode/tasks.json | 25 +++++++ src/db | 2 +- src/serverMain.ts | 56 ++++++++++------ src/templates/server.ts | 108 +++++++++++++++++++++++------ src/{server => }/types.ts | 24 ++++--- src/utils.ts | 138 ++++++++++++++++++++++++++++++++++++++ tests/server.test.ts | 49 ++++++++------ tsconfig.json | 2 +- 9 files changed, 330 insertions(+), 94 deletions(-) create mode 100644 .vscode/tasks.json rename src/{server => }/types.ts (74%) create mode 100644 src/utils.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index ad6a325..4631341 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,25 +10,7 @@ "name": "Launch Server API", "skipFiles": ["/**"], "program": "${workspaceFolder}/src/serverMain.ts", - "preLaunchTask": "tsc: build - tsconfig.json", - "outFiles": ["${workspaceFolder}/out/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Launch Client API", - "skipFiles": ["/**"], - "program": "${workspaceFolder}/src/clientMain.ts", - "preLaunchTask": "tsc: build - tsconfig.json", - "outFiles": ["${workspaceFolder}/out/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Launch Websocket", - "skipFiles": ["/**"], - "program": "${workspaceFolder}/src/websocket.ts", - "preLaunchTask": "tsc: build - tsconfig.json", + "preLaunchTask": "npm: build", "outFiles": ["${workspaceFolder}/out/**/*.js"] } ] diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..887d410 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,25 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "typescript", + "tsconfig": "tsconfig.json", + "problemMatcher": ["$tsc"], + "group": "build", + "label": "tsc: build - tsconfig.json" + }, + { + "label": "myShellCommand", + "type": "shell", + "command": "echo goodfood" + }, + { + "type": "npm", + "script": "build", + "group": "build", + "problemMatcher": [], + "label": "npm: build", + "detail": "npm run generate && npx tsc" + } + ] +} diff --git a/src/db b/src/db index c985feb..dc5325c 160000 --- a/src/db +++ b/src/db @@ -1 +1 @@ -Subproject commit c985feb7261c36cd4e6a38809f4f461e72a402c7 +Subproject commit dc5325c23aa31f85125b35df5441789ddc71e675 diff --git a/src/serverMain.ts b/src/serverMain.ts index 9f8d343..295e4ef 100644 --- a/src/serverMain.ts +++ b/src/serverMain.ts @@ -1,31 +1,43 @@ // Typed routes from https://urosstok.com/blog/typed-routes-in-express +import * as dotenv from "dotenv"; +import express from "express"; +import cors from "cors"; +import { dbReady } from "./db"; +import server from "./generated/server"; +dotenv.config(); +const app = express(); +const port = 3001; -import * as dotenv from 'dotenv' -import express from 'express' -import cors from 'cors' -import { dbReady } from './db/db' -import server from './generated/server' -dotenv.config() +app.use(express.json()); -const app = express() -const port = 3001 +app.use(cors()); +app.get("/", (req, res) => { + res.send("Tone server API online"); +}); -app.use(express.json()) - -app.use(cors()) -app.get('/', (req, res) => { - res.send('Tone server API online') -}) - -app.use('/', server) +app.use("/", (req, res, next) => { + try { + next(); + } catch (error) { + res.status(500).send({ + errors: [ + { + msg: "Internal error", + data: error, + }, + ], + }); + } +}); +app.use("/", server); export default new Promise((resolve, reject) => { void dbReady().then((e) => { - const listenServer = app.listen(port, '0.0.0.0', () => { - console.log(`Tone server api listening on port ${port}`) - resolve(listenServer) - }) - }) -}) + const listenServer = app.listen(port, "0.0.0.0", () => { + console.log(`Tone server api listening on port ${port}`); + resolve(listenServer); + }); + }); +}); diff --git a/src/templates/server.ts b/src/templates/server.ts index 971019c..ca6dd40 100644 --- a/src/templates/server.ts +++ b/src/templates/server.ts @@ -1,10 +1,15 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Router, Request, Response } from "express"; -import { header } from "express-validator"; -import db, { /* CreateKillRecord, */ CheckServerToken } from "../db/db"; +import { header, param } from "express-validator"; +import { + checkOrCreateLoadout, + checkOrCreateWeapon, + checkUpdateOrCreatePlayer, +} from "../utils"; +import db, { /* createKillRecord, */ checkServerToken } from "../db"; import { validateErrors } from "../common"; -import { KillData, MatchData, validateBody } from "../server/types"; -import typia from "typia"; +import { KillData, MatchData, validateBody } from "../types"; +import typia, { validate } from "typia"; const router = Router(); @@ -23,7 +28,7 @@ router.post( if (!req.headers.authorization) { return res.sendStatus(401); } - const query = await CheckServerToken( + const query = await checkServerToken( req.headers.authorization.split(" ")[1] ); if (!query?.host_id) { @@ -85,7 +90,14 @@ router.post( (req: RequestBody, res: Response) => { void (async () => { const host_id = res.locals.host_id; - const { match_id } = req.body; + const { + game_time, + distance, + match_id, + attacker: attackerData, + victim: victimData, + cause_of_death, + } = req.body; const match = await db .selectFrom("ToneAPI_v3.match") .select(["ToneAPI_v3.match.host_id", "ToneAPI_v3.match.match_id"]) @@ -93,20 +105,60 @@ router.post( .where("ToneAPI_v3.match.match_id", "=", match_id) .executeTakeFirst(); if (!match) { - res - .status(403) - .send({ - errors: [ - { - msg: "Match does not exists", - param: "match_id", - location: "body", - }, - ], - }); + res.status(403).send({ + errors: [ + { + msg: "Match does not exists", + param: "match_id", + location: "body", + }, + ], + }); return; } - req.body; + let attacker_loadout: number | undefined; + let victim_loadout: number | undefined; + await Promise.all([ + checkUpdateOrCreatePlayer(attackerData), + checkUpdateOrCreatePlayer(victimData), + checkOrCreateWeapon(cause_of_death), + checkOrCreateWeapon(attackerData.current_weapon.id), + checkOrCreateWeapon(victimData.current_weapon.id), + checkOrCreateLoadout(attackerData.loadout).then( + (e) => (attacker_loadout = e) + ), + checkOrCreateLoadout(victimData.loadout).then( + (e) => (victim_loadout = e) + ), + ]); + if (attacker_loadout === undefined) { + throw new Error("attacker_lodaout is undefined"); + } + if (victim_loadout === undefined) { + throw new Error("victim_lodaout is undefined"); + } + const insertResult = await db + .insertInto("ToneAPI_v3.kill") + .values({ + attacker_id: attackerData.id, + victim_id: victimData.id, + match_id, + attacker_loadout_id: attacker_loadout, + victim_loadout_id: victim_loadout, + attacker_speed: attackerData.velocity, + victim_speed: victimData.velocity, + attacker_movementstate: attackerData.state, + victim_movementstate: victimData.state, + distance, + game_time, + cause_of_death, + attacker_held_weapon: attackerData.current_weapon.id, + victim_held_weapon: victimData.current_weapon.id, + }) + .returning("kill_id") + .executeTakeFirstOrThrow(); + + res.status(201).send({ id: insertResult.kill_id }); })(); } ); @@ -130,11 +182,25 @@ router.post( .values({ host_id, server_name }) .execute(); } + + await db.updateTable("ToneAPI_v3.match") + .set({ ongoing: false }) + .where("ToneAPI_v3.match.server_name", "=", server_name).execute(); + const match = await db .insertInto("ToneAPI_v3.match") - .values({ air_accel, server_name, game_map, gamemode, host_id }) - .executeTakeFirst(); - res.status(201).send({ match: match.insertId }); + .values({ + air_accel, + server_name, + game_map, + gamemode, + host_id, + ongoing: true, + }) + .returning("ToneAPI_v3.match.match_id") + .executeTakeFirstOrThrow(); + + res.status(201).send({ match: match?.match_id }); })(); } ); diff --git a/src/server/types.ts b/src/types.ts similarity index 74% rename from src/server/types.ts rename to src/types.ts index f0f406f..887d619 100644 --- a/src/server/types.ts +++ b/src/types.ts @@ -2,24 +2,28 @@ import { RequestHandler, Request } from "express"; import typia from "typia"; type WeaponKillData = { - name: string; + id: string; mods: number; }; +export type LoadoutKillData = { + ordnance: WeaponKillData | null; + secondary: WeaponKillData | null; + primary: WeaponKillData | null; + tactical: WeaponKillData | null; + anti_titan: WeaponKillData | null; + passive1:string | null; + passive2:string | null; + titan: string | null; +}; + type PlayerKillData = { velocity: number; name: string; - loadout: { - ordnance: WeaponKillData; - secondary: WeaponKillData; - primary: WeaponKillData; - tactical: WeaponKillData; - anti_titan: WeaponKillData; - }; + loadout: LoadoutKillData; current_weapon: WeaponKillData; state: string; - titan: string | null; - id: number | bigint; + id: number; cloaked: boolean; }; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..cc552aa --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,138 @@ +import db from "./db"; +import { LoadoutKillData } from "./types"; + +export async function checkUpdateOrCreatePlayer(data: { + id: number; + name: string; +}) { + const player = await db + .selectFrom("ToneAPI_v3.player") + .select(["player_name"]) + .where("player_id", "=", data.id) + .executeTakeFirst(); + if (!player) { + await db + .insertInto("ToneAPI_v3.player") + .values({ + player_id: data.id, + player_name: data.name, + }) + .execute(); + } else if (player.player_name != data.name) { + await db + .updateTable("ToneAPI_v3.player") + .set({ player_name: data.name }) + .where("ToneAPI_v3.player.player_id", "=", data.id) + .execute(); + } +} + +export async function checkOrCreateWeapon(weapon_id: string) { + const weapon = await db + .selectFrom("ToneAPI_v3.weapon") + .select("ToneAPI_v3.weapon.weapon_id") + .where("weapon_id", "=", weapon_id) + .executeTakeFirst(); + if (!weapon) { + await db + .insertInto("ToneAPI_v3.weapon") + .values({ + weapon_id, + }) + .execute(); + } +} + +export async function checkOrCreateWeaponMods(weapon_mods: { + id: string; + mods: number; +}) { + await checkOrCreateWeapon(weapon_mods.id); + const weaponMods = await db + .selectFrom("ToneAPI_v3.mods_on_weapon") + .select("ToneAPI_v3.mods_on_weapon.mod_id") + .where("mod_id", "=", weapon_mods.mods) + .where("weapon_id", "=", weapon_mods.id) + .executeTakeFirst(); + if (!weaponMods) { + await db + .insertInto("ToneAPI_v3.mods_on_weapon") + .values({ + mod_id: weapon_mods.mods, + weapon_id: weapon_mods.id, + autogenerated: true, + }) + .execute(); + } +} + +export async function checkOrCreateTitan(titan_id: string | null) { + if (titan_id == null) { + return; + } + const titan = await db + .selectFrom("ToneAPI_v3.titan_chassis") + .select("ToneAPI_v3.titan_chassis.titan_id") + .where("titan_id", "=", titan_id) + .executeTakeFirst(); + if (!titan) { + await db + .insertInto("ToneAPI_v3.titan_chassis") + .values({ + titan_id, + }) + .execute(); + } +} + +export async function checkOrCreateLoadout(loadoutData: LoadoutKillData) { + let loadout = await db + .selectFrom("ToneAPI_v3.loadout") + .select("ToneAPI_v3.loadout.loadout_id") + .where("primary_weapon", "=", loadoutData.primary?.id ?? null) + .where("primary_mod_id", "=", loadoutData.primary?.mods ?? null) + .where("secondary_weapon", "=", loadoutData.secondary?.id ?? null) + .where("secondary_mod_id", "=", loadoutData.secondary?.mods ?? null) + .where("anti_titan_weapon", "=", loadoutData.anti_titan?.id ?? null) + .where("anti_titan_mod_id", "=", loadoutData.anti_titan?.mods ?? null) + .where("ToneAPI_v3.loadout.ordnance", "=", loadoutData.ordnance?.id ?? null) + .where("ToneAPI_v3.loadout.tactical", "=", loadoutData.tactical?.id ?? null) + .where("ToneAPI_v3.loadout.pilot_passive_1", "=", loadoutData.passive1) + .where("ToneAPI_v3.loadout.pilot_passive_2", "=", loadoutData.passive2) + .where("ToneAPI_v3.loadout.titan_id", "=", loadoutData.titan) + .executeTakeFirst(); + if (!loadout) { + const promises = new Array>(); + if (loadoutData.primary !== null) { + promises.push(checkOrCreateWeaponMods(loadoutData.primary)); + } + if (loadoutData.secondary !== null) { + promises.push(checkOrCreateWeaponMods(loadoutData.secondary)); + } + if (loadoutData.anti_titan !== null) { + promises.push(checkOrCreateWeaponMods(loadoutData.anti_titan)); + } + if (loadoutData.ordnance !== null) { + promises.push(checkOrCreateWeapon(loadoutData.ordnance.id)); + } + await Promise.all([...promises, checkOrCreateTitan(loadoutData.titan)]); + const result = await db + .insertInto("ToneAPI_v3.loadout") + .values({ + primary_weapon: loadoutData.primary?.id ?? null, + primary_mod_id: loadoutData.primary?.mods ?? null, + secondary_weapon: loadoutData.secondary?.id ?? null, + secondary_mod_id: loadoutData.secondary?.mods ?? null, + anti_titan_weapon: loadoutData.anti_titan?.id ?? null, + anti_titan_mod_id: loadoutData.anti_titan?.mods ?? null, + ordnance: loadoutData.ordnance?.id ?? null, + tactical: loadoutData.tactical?.id ?? null, + pilot_passive_1: loadoutData.passive1, + pilot_passive_2: loadoutData.passive2, + titan_id: loadoutData.titan, + }).returning('ToneAPI_v3.loadout.loadout_id') + .executeTakeFirstOrThrow(); + return result.loadout_id; + } + return loadout.loadout_id +} diff --git a/tests/server.test.ts b/tests/server.test.ts index 18e5ed9..156d1ad 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -1,8 +1,8 @@ import { afterAll, beforeAll, describe, expect, test } from '@jest/globals' import serverMain from '../src/serverMain' -import db from "../src/db/db" +import db, {dbReady} from "../src/db" import * as dotenv from 'dotenv' -import {type KillData} from '../src/server/types' +import {type KillData} from '../src/types' dotenv.config() let listenServer @@ -11,7 +11,7 @@ const headers = { "Content-Type": "application/json", 'Authorization': `Bearer ${Buffer.from(process.env.SERVERAUTH_TOKEN + '').toString('base64')}` } -const testMatch = { air_accel:false, server_name:Math.floor(Math.random()*100).toString(), game_map:"testMap", gamemode:"test" } +const testMatch = { air_accel:false, server_name:"servertest"+Math.floor(Math.random()*100).toString(), game_map:"testMap", gamemode:"test" } const testKill = { "game_time": 22.616668701171876, "player_count": 1, @@ -20,68 +20,74 @@ const testKill = { "velocity": 0.0, "name": "Legonzaur", "loadout": { + "titan":"testTitan", + "passive1":"testPassive1", + "passive2":"testPassive2", "ordnance": { - "name": "mp_weapon_satchel", + "id": "mp_weapon_satchel", "mods": 0 }, "secondary": { - "name": "mp_weapon_wingman", + "id": "mp_weapon_wingman", "mods": 140 }, "primary": { - "name": "mp_weapon_sniper", + "id": "mp_weapon_sniper", "mods": 1168 }, "tactical": { - "name": "mp_ability_grapple", + "id": "mp_ability_grapple", "mods": 8 }, "anti_titan": { - "name": "mp_weapon_defender", + "id": "mp_weapon_defender", "mods": 134 } }, "current_weapon": { - "name": "mp_weapon_sniper", + "id": "mp_weapon_sniper", "mods": 1168 }, "state": "OnGround", "titan": "null", - "id": 1005930844007n, + "id": 1005930844007, "cloaked": false }, "attacker": { "velocity": 0.0, "name": "Legonzaur", "loadout": { + "titan":"testTitan", + "passive1":"testPassive1", + "passive2":"testPassive2", "ordnance": { - "name": "mp_weapon_satchel", + "id": "mp_weapon_satchel", "mods": 0 }, "secondary": { - "name": "mp_weapon_wingman", + "id": "mp_weapon_wingman", "mods": 140 }, "primary": { - "name": "mp_weapon_sniper", + "id": "mp_weapon_sniper", "mods": 1168 }, "tactical": { - "name": "mp_ability_grapple", + "id": "mp_ability_grapple", "mods": 8 }, "anti_titan": { - "name": "mp_weapon_defender", + "id": "mp_weapon_defender", "mods": 134 } }, "current_weapon": { - "name": "mp_weapon_sniper", + "id": "mp_weapon_sniper", "mods": 1168 }, "state": "OnGround", "titan": "null", - "id": 1005930844007n, + "id": 1005930844007, "cloaked": false }, "distance": 0.0, @@ -91,6 +97,7 @@ const testKill = { beforeAll(async () => { listenServer = await serverMain; + await dbReady() }) describe('server', () => { @@ -114,7 +121,7 @@ describe('server', () => { 'Authorization': `Bearer ${Buffer.from('badtoken').toString('base64')}` } }); - expect(response2.status).toBe(403) + expect(response2.status).toBe(401) }) test('bad kill token', async () => { @@ -126,7 +133,7 @@ describe('server', () => { 'Authorization': `Bearer ${Buffer.from('badtoken').toString('base64')}` } }); - expect(response2.status).toBe(403) + expect(response2.status).toBe(401) }) test('good auth prefetch', async () => { @@ -144,7 +151,9 @@ describe('server', () => { headers, body: JSON.stringify(testMatch), }); - expect(await response.json()).toHaveProperty("match") + const json = await response.json() + expect(json).toHaveProperty("match") + expect(json.match).not.toBeNaN() expect(response.status).toBe(201) }) diff --git a/tsconfig.json b/tsconfig.json index a6b7049..0d22d81 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -101,5 +101,5 @@ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, "compileOnSave": true, - "exclude": ["jest.config.ts", "./tests"] + "exclude": ["jest.config.ts", "./tests", "./src/templates"] }