Lanista match details submit untested version

This commit is contained in:
Kalle 2022-03-07 21:01:24 +02:00
parent 832af6e84c
commit e9d650ad3d
6 changed files with 319 additions and 55 deletions

View File

@ -1,3 +1,5 @@
import { Ability } from "@prisma/client";
export const DISCORD_URL = "https://discord.gg/sendou";
export const ADMIN_TEST_UUID = "846e12eb-d373-4002-a0c3-e23077e1c88c";
@ -208,3 +210,33 @@ export const weapons = [
"Hero Brella Replica",
"Octo Shot Replica",
] as const;
export const abilities: Ability[] = [
"ISM",
"ISS",
"REC",
"RSU",
"SSU",
"SCU",
"SS",
"SPU",
"QR",
"QSJ",
"BRU",
"RES",
"BDU",
"MPU",
"OG",
"LDE",
"T",
"CB",
"NS",
"H",
"TI",
"RP",
"AD",
"SJ",
"OS",
"DR",
"EMPTY",
];

View File

@ -0,0 +1,52 @@
import { Ability } from "@prisma/client";
import { db } from "~/utils/db.server";
export interface CreateGameDetailsInput {
id: string;
duration: number;
startedAt: Date;
lfgStageId: string;
teams: {
id: string;
isWinner: boolean;
score: number;
players: {
principalId: string;
name: string;
weapon: string;
mainAbilities: Ability[];
subAbilities: Ability[];
kills: number;
assists: number;
deaths: number;
specials: number;
paint: number;
gear: string[];
}[];
}[];
}
export function create(details: CreateGameDetailsInput[]) {
return db.$transaction([
db.gameDetail.createMany({
data: details.map(({ teams: _teams, ...detail }) => detail),
}),
db.gameDetailTeam.createMany({
data: details.flatMap((detail) =>
detail.teams.map(({ players: _players, ...team }) => ({
gameDetailId: detail.id,
...team,
}))
),
}),
db.gameDetailPlayer.createMany({
data: details
.flatMap((detail) => detail.teams)
.flatMap((team) =>
team.players.map((player) => ({
gameDetailTeamId: team.id,
...player,
}))
),
}),
]);
}

View File

@ -10,8 +10,10 @@ export function findById(id: string) {
createdAt: true,
stages: {
select: {
id: true,
stage: {
select: {
id: true,
name: true,
mode: true,
},

View File

@ -1,44 +1,22 @@
import type { ActionFunction } from "remix";
import { z } from "zod";
import { weapons } from "~/constants";
import { modesShort, stages } from "~/core/stages/stages";
import { abilities, weapons } from "~/constants";
import { idToStage, modesShort, stages } from "~/core/stages/stages";
import { parseRequestFormData } from "~/utils";
import { v4 as uuidv4 } from "uuid";
import * as LFGMatch from "~/models/LFGMatch.server";
import * as GameDetail from "~/models/GameDetail.server";
import invariant from "tiny-invariant";
import { Ability } from "@prisma/client";
export const abilityEnum = z.enum([
"ISM",
"ISS",
"REC",
"RSU",
"SSU",
"SCU",
"SS",
"SPU",
"QR",
"QSJ",
"BRU",
"RES",
"BDU",
"MPU",
"OG",
"LDE",
"T",
"CB",
"NS",
"H",
"TI",
"RP",
"AD",
"SJ",
"OS",
"DR",
]);
const abilityEnum = z.enum(abilities as [Ability, ...Ability[]]);
const playerSchema = z.object({
principal_id: z.string(),
name: z.string().min(1).max(10),
weapon: z.enum(weapons),
main_abilities: z.array(abilityEnum),
sub_abilities: z.array(z.array(abilityEnum)),
main_abilities: z.array(abilityEnum).length(3),
sub_abilities: z.array(abilityEnum).length(9),
kills: z.number().int().min(0).max(50),
assists: z.number().int().min(0).max(50),
deaths: z.number().int().min(0).max(50),
@ -52,30 +30,28 @@ const teamInfoSchema = z.object({
players: z.array(playerSchema),
});
export const detailedMapSchema = z.array(
z.object({
stage: z.enum(stages),
mode: z.enum(modesShort as [string, ...string[]]),
duration: z.number().int().min(15).max(500),
winners: teamInfoSchema,
losers: teamInfoSchema,
date: z.string().refine((val) => {
const d = new Date(Number(val));
if (Number.isNaN(d.getTime())) {
return false;
}
export const detailedMapSchema = z.object({
stage: z.enum(stages),
mode: z.enum(modesShort as [string, ...string[]]),
duration: z.number().int().min(15).max(500),
winners: teamInfoSchema,
losers: teamInfoSchema,
date: z.string().refine((val) => {
const d = new Date(Number(val));
if (Number.isNaN(d.getTime())) {
return false;
}
const nd = new Date();
nd.setMonth(-6);
const nd = new Date();
nd.setMonth(-6);
if (d.getTime() < nd.getTime()) {
return false;
}
if (d.getTime() < nd.getTime()) {
return false;
}
return true;
}),
})
);
return true;
}),
});
const matchDetailsSchema = z.object({
token: z.string(),
@ -86,15 +62,81 @@ const matchDetailsSchema = z.object({
});
export const action: ActionFunction = async ({ request }) => {
const data = await parseRequestFormData({
const input = await parseRequestFormData({
request,
schema: matchDetailsSchema,
useBody: true,
});
if (data.token !== process.env.LANISTA_TOKEN) {
if (input.token !== process.env.LANISTA_TOKEN) {
return new Response(null, { status: 401 });
}
const match = await LFGMatch.findById(input.data.matchId);
if (!match) {
return new Response("Invalid match id", { status: 400 });
}
const expectedMapsCount = match.stages.reduce(
(acc, cur) => Number(Boolean(cur.winnerGroupId)) + acc,
0
);
if (expectedMapsCount !== input.data.maps.length) {
return new Response(
`Incorrect amount of maps provided. Expected ${expectedMapsCount} got ${input.data.maps.length}`,
{ status: 400 }
);
}
for (const [i, map] of input.data.maps.entries()) {
const stageObj = idToStage(match.stages[i].stage.id);
if (stageObj.name === map.stage && stageObj.mode === map.mode) {
continue;
}
return new Response(
`In position ${i + 1} expected ${stageObj.mode} ${
stageObj.name
} but got ${map.mode} ${map.stage}`,
{ status: 400 }
);
}
const createGameDetailsInput: GameDetail.CreateGameDetailsInput[] = [];
for (const [i, map] of input.data.maps.entries()) {
const lfgStage = match.stages[i];
invariant(lfgStage, "Unexpected lfgStage undefined");
createGameDetailsInput.push({
id: uuidv4(),
duration: map.duration,
lfgStageId: lfgStage.id,
startedAt: new Date(map.date),
teams: [map.winners, map.losers].map((team, i) => {
return {
id: uuidv4(),
isWinner: i === 0,
score: team.score,
players: team.players.map((player) => ({
principalId: player.principal_id,
name: player.name,
weapon: player.weapon,
mainAbilities: player.main_abilities,
subAbilities: player.sub_abilities,
kills: player.kills,
assists: player.assists,
deaths: player.deaths,
specials: player.specials,
paint: player.paint,
gear: player.gear,
})),
};
}),
});
}
await GameDetail.create(createGameDetailsInput);
return new Response(null, { status: 204 });
};

View File

@ -0,0 +1,65 @@
/*
Warnings:
- The required column `id` was added to the `LfgGroupMatchStage` table with a prisma-level default value. This is not possible if the table is not empty. Please add this column as optional, then populate it before making it required.
*/
-- CreateEnum
CREATE TYPE "Ability" AS ENUM ('CB', 'LDE', 'OG', 'T', 'H', 'NS', 'TI', 'RP', 'AD', 'DR', 'SJ', 'OS', 'BDU', 'REC', 'RES', 'ISM', 'ISS', 'MPU', 'QR', 'QSJ', 'RSU', 'SSU', 'SCU', 'SPU', 'SS', 'BRU', 'EMPTY');
-- AlterTable
ALTER TABLE "LfgGroupMatchStage" ADD COLUMN "id" TEXT NOT NULL,
ADD CONSTRAINT "LfgGroupMatchStage_pkey" PRIMARY KEY ("id");
-- AlterTable
ALTER TABLE "User" ADD COLUMN "bannedUntil" TIMESTAMP(3);
-- CreateTable
CREATE TABLE "GameDetail" (
"id" TEXT NOT NULL,
"duration" INTEGER NOT NULL,
"startedAt" TIMESTAMP(3) NOT NULL,
"lfgStageId" TEXT,
CONSTRAINT "GameDetail_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GameDetailTeam" (
"id" TEXT NOT NULL,
"gameDetailId" TEXT NOT NULL,
"isWinner" BOOLEAN NOT NULL,
"score" INTEGER NOT NULL,
CONSTRAINT "GameDetailTeam_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GameDetailPlayer" (
"gameDetailTeamId" TEXT NOT NULL,
"principalId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"weapon" TEXT NOT NULL,
"mainAbilities" "Ability"[],
"subAbilities" "Ability"[],
"kills" INTEGER NOT NULL,
"assists" INTEGER NOT NULL,
"deaths" INTEGER NOT NULL,
"specials" INTEGER NOT NULL,
"paint" INTEGER NOT NULL,
"gear" TEXT[],
CONSTRAINT "GameDetailPlayer_pkey" PRIMARY KEY ("gameDetailTeamId","principalId")
);
-- CreateIndex
CREATE UNIQUE INDEX "GameDetail_lfgStageId_key" ON "GameDetail"("lfgStageId");
-- CreateIndex
CREATE UNIQUE INDEX "GameDetailTeam_gameDetailId_isWinner_key" ON "GameDetailTeam"("gameDetailId", "isWinner");
-- AddForeignKey
ALTER TABLE "GameDetail" ADD CONSTRAINT "GameDetail_lfgStageId_fkey" FOREIGN KEY ("lfgStageId") REFERENCES "LfgGroupMatchStage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GameDetailTeam" ADD CONSTRAINT "GameDetailTeam_gameDetailId_fkey" FOREIGN KEY ("gameDetailId") REFERENCES "GameDetail"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -22,6 +22,7 @@ model User {
weapons String[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
bannedUntil DateTime?
trustedUsers TrustRelationships[] @relation("trustGiver")
trustingUsers TrustRelationships[] @relation("trustReceiver")
ownedOrganization Organization?
@ -272,6 +273,7 @@ model LfgGroupMatch {
}
model LfgGroupMatchStage {
id String @id @default(uuid())
lfgGroupMatchId String
stageId Int
order Int
@ -279,6 +281,7 @@ model LfgGroupMatchStage {
lfgGroupMatch LfgGroupMatch @relation(fields: [lfgGroupMatchId], references: [id])
stage Stage @relation(fields: [stageId], references: [id])
winnerGroup LfgGroup? @relation(fields: [winnerGroupId], references: [id])
details GameDetail[]
@@unique([lfgGroupMatchId, order])
}
@ -299,3 +302,71 @@ model Skill {
@@unique([userId, matchId])
@@unique([userId, tournamentId])
}
model GameDetail {
id String @id @default(uuid())
duration Int
startedAt DateTime
lfgStageId String?
teams GameDetailTeam[]
lfgStage LfgGroupMatchStage? @relation(fields: [lfgStageId], references: [id])
@@unique([lfgStageId])
}
model GameDetailTeam {
id String @id @default(uuid())
gameDetailId String
isWinner Boolean
score Int
gameDetails GameDetail @relation(fields: [gameDetailId], references: [id])
@@unique([gameDetailId, isWinner])
}
enum Ability {
CB
LDE
OG
T
H
NS
TI
RP
AD
DR
SJ
OS
BDU
REC
RES
ISM
ISS
MPU
QR
QSJ
RSU
SSU
SCU
SPU
SS
BRU
EMPTY
}
model GameDetailPlayer {
gameDetailTeamId String
principalId String
name String
weapon String
mainAbilities Ability[]
subAbilities Ability[]
kills Int
assists Int
deaths Int
specials Int
paint Int
gear String[]
@@id([gameDetailTeamId, principalId])
}