mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-20 18:29:48 -05:00
Lanista match details submit untested version
This commit is contained in:
parent
832af6e84c
commit
e9d650ad3d
|
|
@ -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",
|
||||
];
|
||||
|
|
|
|||
52
app/models/GameDetail.server.ts
Normal file
52
app/models/GameDetail.server.ts
Normal 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,
|
||||
}))
|
||||
),
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
|
@ -10,8 +10,10 @@ export function findById(id: string) {
|
|||
createdAt: true,
|
||||
stages: {
|
||||
select: {
|
||||
id: true,
|
||||
stage: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
mode: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
};
|
||||
|
|
|
|||
65
prisma/migrations/20220307165333_add_details/migration.sql
Normal file
65
prisma/migrations/20220307165333_add_details/migration.sql
Normal 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;
|
||||
|
|
@ -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])
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user