mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-08 12:16:12 -05:00
Replace the old sql queries with kysely queries for the VODs feature (#2423)
This commit is contained in:
@@ -45,7 +45,7 @@ import { TOURNAMENT } from "~/features/tournament/tournament-constants";
|
||||
import { clearAllTournamentDataCache } from "~/features/tournament-bracket/core/Tournament.server";
|
||||
import * as TournamentOrganizationRepository from "~/features/tournament-organization/TournamentOrganizationRepository.server";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
import { createVod } from "~/features/vods/queries/createVod.server";
|
||||
import * as VodRepository from "~/features/vods/VodRepository.server";
|
||||
import {
|
||||
secondsToHoursMinutesSecondString,
|
||||
youtubeIdToYoutubeUrl,
|
||||
@@ -1722,8 +1722,8 @@ function otherTeams() {
|
||||
}
|
||||
}
|
||||
|
||||
function realVideo() {
|
||||
createVod({
|
||||
async function realVideo() {
|
||||
await VodRepository.createVod({
|
||||
type: "TOURNAMENT",
|
||||
youtubeUrl: youtubeIdToYoutubeUrl("M4aV-BQWlVg"),
|
||||
date: { day: 2, month: 2, year: 2023 },
|
||||
@@ -1776,8 +1776,8 @@ function realVideo() {
|
||||
});
|
||||
}
|
||||
|
||||
function realVideoCast() {
|
||||
createVod({
|
||||
async function realVideoCast() {
|
||||
await VodRepository.createVod({
|
||||
type: "CAST",
|
||||
youtubeUrl: youtubeIdToYoutubeUrl("M4aV-BQWlVg"),
|
||||
date: { day: 2, month: 2, year: 2023 },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
import { findVods } from "~/features/vods/queries/findVods.server";
|
||||
import * as VodRepository from "~/features/vods/VodRepository.server";
|
||||
import { notFoundIfFalsy } from "~/utils/remix.server";
|
||||
|
||||
export const loader = async ({ params }: LoaderFunctionArgs) => {
|
||||
@@ -10,6 +10,6 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
||||
|
||||
return {
|
||||
// TODO: add pagination instead of not showing oldest vods at all
|
||||
vods: findVods({ userId, limit: 100 }),
|
||||
vods: await VodRepository.findVodsByUserId(userId),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,273 @@
|
||||
import { expressionBuilder, sql } from "kysely";
|
||||
import { jsonArrayFrom } from "kysely/helpers/sqlite";
|
||||
import * as R from "remeda";
|
||||
import { db } from "~/db/sql";
|
||||
import type { DB, Tables } from "~/db/tables";
|
||||
import type {
|
||||
MainWeaponId,
|
||||
ModeShort,
|
||||
StageId,
|
||||
} from "~/modules/in-game-lists/types";
|
||||
import { weaponIdToArrayWithAlts } from "~/modules/in-game-lists/weapon-ids";
|
||||
import {
|
||||
dateToDatabaseTimestamp,
|
||||
dayMonthYearToDatabaseTimestamp,
|
||||
} from "~/utils/dates";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { VODS_PAGE_BATCH_SIZE } from "./vods-constants";
|
||||
import type { VideoBeingAdded, Vod } from "./vods-types";
|
||||
import {
|
||||
extractYoutubeIdFromVideoUrl,
|
||||
hoursMinutesSecondsStringToSeconds,
|
||||
} from "./vods-utils";
|
||||
|
||||
export function deleteById(id: number) {
|
||||
return db.deleteFrom("UnvalidatedVideo").where("id", "=", id).execute();
|
||||
}
|
||||
|
||||
export async function findVodsByUserId(
|
||||
userId: Tables["User"]["id"],
|
||||
limit = 100,
|
||||
) {
|
||||
return findVods({ userId, limit });
|
||||
}
|
||||
|
||||
export async function findVods({
|
||||
weapon,
|
||||
mode,
|
||||
stageId,
|
||||
type,
|
||||
userId,
|
||||
limit = VODS_PAGE_BATCH_SIZE,
|
||||
}: {
|
||||
weapon?: MainWeaponId;
|
||||
mode?: ModeShort;
|
||||
stageId?: StageId;
|
||||
type?: Tables["Video"]["type"];
|
||||
userId?: number;
|
||||
limit?: number;
|
||||
}) {
|
||||
let query = db
|
||||
.selectFrom("Video")
|
||||
.leftJoin("VideoMatch", "Video.id", "VideoMatch.videoId")
|
||||
.leftJoin(
|
||||
"VideoMatchPlayer",
|
||||
"VideoMatch.id",
|
||||
"VideoMatchPlayer.videoMatchId",
|
||||
)
|
||||
.leftJoin("User", "VideoMatchPlayer.playerUserId", "User.id")
|
||||
.selectAll("Video")
|
||||
.select(({ fn, ref }) => [
|
||||
sql<
|
||||
Array<number>
|
||||
>`json_group_array(distinct ${ref("VideoMatchPlayer.weaponSplId")})`
|
||||
.$castTo<MainWeaponId[]>()
|
||||
.as("weapons"),
|
||||
fn
|
||||
.agg("json_group_array", ["VideoMatchPlayer.playerName"])
|
||||
.$castTo<string[]>()
|
||||
.as("playerNames"),
|
||||
selectPlayers(),
|
||||
]);
|
||||
if (userId) {
|
||||
query = query.where("User.id", "=", userId);
|
||||
} else {
|
||||
if (type) {
|
||||
query = query.where("Video.type", "=", type);
|
||||
}
|
||||
if (mode) {
|
||||
query = query.where("VideoMatch.mode", "=", mode);
|
||||
}
|
||||
if (stageId) {
|
||||
query = query.where("VideoMatch.stageId", "=", stageId);
|
||||
}
|
||||
}
|
||||
if (weapon) {
|
||||
query = query.where(
|
||||
"VideoMatchPlayer.weaponSplId",
|
||||
"in",
|
||||
weaponIdToArrayWithAlts(weapon),
|
||||
);
|
||||
}
|
||||
const result = await query
|
||||
.groupBy("Video.id")
|
||||
.orderBy("Video.youtubeDate", "desc")
|
||||
.limit(limit)
|
||||
.execute();
|
||||
|
||||
const vods = result.map((value) => {
|
||||
const { playerNames, players, ...vod } = value;
|
||||
return {
|
||||
...vod,
|
||||
pov: playerNames[0] ?? players[0],
|
||||
};
|
||||
});
|
||||
return vods;
|
||||
}
|
||||
|
||||
export async function findVodById(id: Tables["Video"]["id"]) {
|
||||
const videoQuery = db
|
||||
.selectFrom("Video")
|
||||
.select([
|
||||
"id",
|
||||
"title",
|
||||
"youtubeDate",
|
||||
"youtubeId",
|
||||
"type",
|
||||
"submitterUserId",
|
||||
])
|
||||
.where("Video.id", "=", id);
|
||||
|
||||
const video = await videoQuery.executeTakeFirst();
|
||||
|
||||
if (video) {
|
||||
const videoMatchQuery = db
|
||||
.selectFrom("VideoMatch")
|
||||
.select([
|
||||
"VideoMatch.id",
|
||||
"VideoMatch.mode",
|
||||
"VideoMatch.stageId",
|
||||
"VideoMatch.startsAt",
|
||||
])
|
||||
.leftJoin(
|
||||
"VideoMatchPlayer",
|
||||
"VideoMatch.id",
|
||||
"VideoMatchPlayer.videoMatchId",
|
||||
)
|
||||
.leftJoin("User", "VideoMatchPlayer.playerUserId", "User.id")
|
||||
.select(selectPlayers())
|
||||
.select(({ fn }) => [
|
||||
fn
|
||||
.agg("json_group_array", ["VideoMatchPlayer.weaponSplId"])
|
||||
.$castTo<MainWeaponId[]>()
|
||||
.as("weapons"),
|
||||
fn
|
||||
.agg("json_group_array", ["VideoMatchPlayer.playerName"])
|
||||
.as("playerNames"),
|
||||
])
|
||||
.where("VideoMatch.videoId", "=", id)
|
||||
.groupBy("VideoMatch.id")
|
||||
.orderBy("VideoMatch.startsAt", "asc")
|
||||
.orderBy("VideoMatchPlayer.player", "asc");
|
||||
|
||||
const matches = await videoMatchQuery.execute();
|
||||
|
||||
return {
|
||||
...video,
|
||||
pov: resolvePov(matches),
|
||||
matches: R.map(matches, R.omit(["players", "playerNames"])),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolvePov(matches: any): Vod["pov"] {
|
||||
for (const match of matches) {
|
||||
if (match.playerNames.length > 0) {
|
||||
return match.playerNames[0];
|
||||
}
|
||||
|
||||
if (match.players.length > 0) {
|
||||
return match.players[0];
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
export async function updateVodByReplacing(
|
||||
args: VideoBeingAdded & {
|
||||
submitterUserId: number;
|
||||
isValidated: boolean;
|
||||
id: number;
|
||||
},
|
||||
) {
|
||||
return createVod(args);
|
||||
}
|
||||
|
||||
export async function createVod(
|
||||
args: VideoBeingAdded & {
|
||||
submitterUserId: number;
|
||||
isValidated: boolean;
|
||||
id?: number;
|
||||
},
|
||||
) {
|
||||
const youtubeId = extractYoutubeIdFromVideoUrl(args.youtubeUrl);
|
||||
invariant(youtubeId, "Invalid YouTube URL");
|
||||
return db.transaction().execute(async (trx) => {
|
||||
let videoId: number;
|
||||
const video = {
|
||||
title: args.title,
|
||||
type: args.type,
|
||||
youtubeDate: dayMonthYearToDatabaseTimestamp(args.date),
|
||||
eventId: args.eventId ?? null,
|
||||
youtubeId,
|
||||
submitterUserId: args.submitterUserId,
|
||||
validatedAt: args.isValidated
|
||||
? dateToDatabaseTimestamp(new Date())
|
||||
: null,
|
||||
};
|
||||
if (args.id) {
|
||||
await trx
|
||||
.deleteFrom("VideoMatch")
|
||||
.where("videoId", "=", args.id)
|
||||
.execute();
|
||||
|
||||
await trx
|
||||
.updateTable("UnvalidatedVideo")
|
||||
.set(video)
|
||||
.where("id", "=", args.id)
|
||||
.execute();
|
||||
videoId = args.id;
|
||||
} else {
|
||||
const result = await trx
|
||||
.insertInto("UnvalidatedVideo")
|
||||
.values(video)
|
||||
.returning("UnvalidatedVideo.id")
|
||||
.executeTakeFirstOrThrow();
|
||||
videoId = result.id;
|
||||
}
|
||||
for (const match of args.matches) {
|
||||
const videoMatchResult = await trx
|
||||
.insertInto("VideoMatch")
|
||||
.values({
|
||||
videoId: videoId,
|
||||
startsAt: hoursMinutesSecondsStringToSeconds(match.startsAt),
|
||||
stageId: match.stageId,
|
||||
mode: match.mode,
|
||||
})
|
||||
.returning("VideoMatch.id")
|
||||
.executeTakeFirstOrThrow();
|
||||
const matchId = videoMatchResult.id;
|
||||
|
||||
for (const [i, weaponSplId] of match.weapons.entries()) {
|
||||
await trx
|
||||
.insertInto("VideoMatchPlayer")
|
||||
.values({
|
||||
videoMatchId: matchId,
|
||||
playerUserId: args.pov?.type === "USER" ? args.pov.userId : null,
|
||||
playerName: args.pov?.type === "NAME" ? args.pov.name : null,
|
||||
weaponSplId,
|
||||
player: i + 1,
|
||||
})
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
}
|
||||
return { ...video, id: videoId };
|
||||
});
|
||||
}
|
||||
|
||||
function selectPlayers() {
|
||||
const eb = expressionBuilder<DB>();
|
||||
|
||||
return jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("User")
|
||||
.select([
|
||||
"User.username",
|
||||
"User.discordId",
|
||||
"User.discordAvatar",
|
||||
"User.customUrl",
|
||||
]),
|
||||
).as("players");
|
||||
}
|
||||
|
||||
@@ -2,14 +2,15 @@ import { type ActionFunctionArgs, redirect } from "@remix-run/node";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import { badRequestIfFalsy, unauthorizedIfFalsy } from "~/utils/remix.server";
|
||||
import { userVodsPage } from "~/utils/urls";
|
||||
import { findVodById } from "../queries/findVodById.server";
|
||||
import * as VodRepository from "../VodRepository.server";
|
||||
import { canEditVideo } from "../vods-utils";
|
||||
|
||||
export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
const user = await requireUser(request);
|
||||
|
||||
const vod = badRequestIfFalsy(findVodById(Number(params.id)));
|
||||
const vod = badRequestIfFalsy(
|
||||
await VodRepository.findVodById(Number(params.id)),
|
||||
);
|
||||
|
||||
unauthorizedIfFalsy(
|
||||
canEditVideo({
|
||||
|
||||
@@ -4,8 +4,7 @@ import { requireUser } from "~/features/auth/core/user.server";
|
||||
import { requireRole } from "~/modules/permissions/guards.server";
|
||||
import { notFoundIfFalsy, parseRequestPayload } from "~/utils/remix.server";
|
||||
import { vodVideoPage } from "~/utils/urls";
|
||||
import { createVod, updateVodByReplacing } from "../queries/createVod.server";
|
||||
import { findVodById } from "../queries/findVodById.server";
|
||||
import * as VodRepository from "../VodRepository.server";
|
||||
import { videoInputSchema } from "../vods-schemas";
|
||||
import { canEditVideo } from "../vods-utils";
|
||||
|
||||
@@ -20,7 +19,9 @@ export const action: ActionFunction = async ({ request }) => {
|
||||
|
||||
let video: Tables["Video"];
|
||||
if (data.vodToEditId) {
|
||||
const vod = notFoundIfFalsy(findVodById(data.vodToEditId));
|
||||
const vod = notFoundIfFalsy(
|
||||
await VodRepository.findVodById(data.vodToEditId),
|
||||
);
|
||||
|
||||
if (
|
||||
!canEditVideo({
|
||||
@@ -32,14 +33,14 @@ export const action: ActionFunction = async ({ request }) => {
|
||||
throw new Response("no permissions to edit this vod", { status: 401 });
|
||||
}
|
||||
|
||||
video = updateVodByReplacing({
|
||||
video = await VodRepository.updateVodByReplacing({
|
||||
...data.video,
|
||||
submitterUserId: user.id,
|
||||
isValidated: true,
|
||||
id: data.vodToEditId,
|
||||
});
|
||||
} else {
|
||||
video = createVod({
|
||||
video = await VodRepository.createVod({
|
||||
...data.video,
|
||||
submitterUserId: user.id,
|
||||
isValidated: true,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { notFoundIfFalsy } from "~/utils/remix.server";
|
||||
import { findVodById } from "../queries/findVodById.server";
|
||||
import * as VodRepository from "../VodRepository.server";
|
||||
|
||||
export const loader = ({ params }: LoaderFunctionArgs) => {
|
||||
const vod = notFoundIfFalsy(findVodById(Number(params.id)));
|
||||
export const loader = async ({ params }: LoaderFunctionArgs) => {
|
||||
const vod = notFoundIfFalsy(
|
||||
await VodRepository.findVodById(Number(params.id)),
|
||||
);
|
||||
|
||||
return { vod };
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { z } from "zod/v4";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import { notFoundIfFalsy } from "~/utils/remix.server";
|
||||
import { actualNumber, id } from "~/utils/zod";
|
||||
import { findVodById } from "../queries/findVodById.server";
|
||||
import * as VodRepository from "../VodRepository.server";
|
||||
import { canEditVideo, vodToVideoBeingAdded } from "../vods-utils";
|
||||
|
||||
const newVodLoaderParamsSchema = z.object({
|
||||
@@ -22,7 +22,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
return { vodToEdit: null };
|
||||
}
|
||||
|
||||
const vod = notFoundIfFalsy(findVodById(params.data.vod));
|
||||
const vod = notFoundIfFalsy(await VodRepository.findVodById(params.data.vod));
|
||||
const vodToEdit = vodToVideoBeingAdded(vod);
|
||||
|
||||
if (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { findVods } from "../queries/findVods.server";
|
||||
import * as VodRepository from "../VodRepository.server";
|
||||
import { VODS_PAGE_BATCH_SIZE } from "../vods-constants";
|
||||
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
@@ -7,7 +7,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
|
||||
const limit = Number(url.searchParams.get("limit") ?? VODS_PAGE_BATCH_SIZE);
|
||||
|
||||
const vods = findVods({
|
||||
const vods = await VodRepository.findVods({
|
||||
...Object.fromEntries(
|
||||
Array.from(url.searchParams.entries()).filter(([, value]) => value),
|
||||
),
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import {
|
||||
dateToDatabaseTimestamp,
|
||||
dayMonthYearToDatabaseTimestamp,
|
||||
} from "~/utils/dates";
|
||||
import invariant from "~/utils/invariant";
|
||||
import type { VideoBeingAdded } from "../vods-types";
|
||||
import {
|
||||
extractYoutubeIdFromVideoUrl,
|
||||
hoursMinutesSecondsStringToSeconds,
|
||||
} from "../vods-utils";
|
||||
|
||||
const createVideoStm = sql.prepare(/* sql */ `
|
||||
insert into "UnvalidatedVideo"
|
||||
("id", "title", "type", "youtubeDate", "eventId", "youtubeId", "submitterUserId", "validatedAt")
|
||||
values
|
||||
(@id, @title, @type, @youtubeDate, @eventId, @youtubeId, @submitterUserId, @validatedAt)
|
||||
returning *
|
||||
`);
|
||||
|
||||
const deleteVideoStm = sql.prepare(/* sql */ `
|
||||
delete from "UnvalidatedVideo"
|
||||
where "id" = @id
|
||||
`);
|
||||
|
||||
const createVideoMatchStm = sql.prepare(/* sql */ `
|
||||
insert into "VideoMatch"
|
||||
("videoId", "startsAt", "stageId", "mode")
|
||||
values
|
||||
(@videoId, @startsAt, @stageId, @mode)
|
||||
returning *
|
||||
`);
|
||||
|
||||
const createVideoMatchPlayerStm = sql.prepare(/* sql */ `
|
||||
insert into "VideoMatchPlayer"
|
||||
("videoMatchId", "playerUserId", "playerName", "weaponSplId", "player")
|
||||
values
|
||||
(@videoMatchId, @playerUserId, @playerName, @weaponSplId, @player)
|
||||
`);
|
||||
|
||||
export const createVod = sql.transaction(
|
||||
(
|
||||
args: VideoBeingAdded & {
|
||||
submitterUserId: number;
|
||||
isValidated: boolean;
|
||||
id?: number;
|
||||
},
|
||||
) => {
|
||||
const youtubeId = extractYoutubeIdFromVideoUrl(args.youtubeUrl);
|
||||
invariant(youtubeId, "Invalid YouTube URL");
|
||||
|
||||
const video = createVideoStm.get({
|
||||
id: args.id ?? null,
|
||||
title: args.title,
|
||||
type: args.type,
|
||||
youtubeDate: dayMonthYearToDatabaseTimestamp(args.date),
|
||||
eventId: args.eventId ?? null,
|
||||
youtubeId,
|
||||
submitterUserId: args.submitterUserId,
|
||||
validatedAt: args.isValidated
|
||||
? dateToDatabaseTimestamp(new Date())
|
||||
: null,
|
||||
}) as Tables["Video"];
|
||||
|
||||
for (const match of args.matches) {
|
||||
const videoMatch = createVideoMatchStm.get({
|
||||
videoId: video.id,
|
||||
startsAt: hoursMinutesSecondsStringToSeconds(match.startsAt),
|
||||
stageId: match.stageId,
|
||||
mode: match.mode,
|
||||
}) as any;
|
||||
|
||||
for (const [i, weaponSplId] of match.weapons.entries()) {
|
||||
createVideoMatchPlayerStm.run({
|
||||
videoMatchId: videoMatch.id,
|
||||
playerUserId: args.pov?.type === "USER" ? args.pov.userId : null,
|
||||
playerName: args.pov?.type === "NAME" ? args.pov.name : null,
|
||||
weaponSplId,
|
||||
player: i + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return video;
|
||||
},
|
||||
);
|
||||
|
||||
export const updateVodByReplacing = sql.transaction(
|
||||
(
|
||||
args: VideoBeingAdded & {
|
||||
submitterUserId: number;
|
||||
isValidated: boolean;
|
||||
id: number;
|
||||
},
|
||||
) => {
|
||||
deleteVideoStm.run({ id: args.id });
|
||||
const video = createVod(args);
|
||||
|
||||
return video;
|
||||
},
|
||||
);
|
||||
@@ -1,77 +0,0 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import { parseDBArray } from "~/utils/sql";
|
||||
import type { Vod } from "../vods-types";
|
||||
|
||||
const videoStm = sql.prepare(/* sql */ `
|
||||
select
|
||||
v."id",
|
||||
v."title",
|
||||
v."youtubeDate",
|
||||
v."youtubeId",
|
||||
v."type",
|
||||
v."submitterUserId"
|
||||
from "Video" v
|
||||
where v."id" = @id
|
||||
`);
|
||||
|
||||
const videoMatchesStm = sql.prepare(/* sql */ `
|
||||
select
|
||||
vm."id",
|
||||
vm."mode",
|
||||
vm."stageId",
|
||||
vm."startsAt",
|
||||
json_group_array("vp"."weaponSplId") as "weapons",
|
||||
json_group_array("vp"."playerName") as "playerNames",
|
||||
json_group_array(
|
||||
json_object(
|
||||
'username',
|
||||
"u"."username",
|
||||
'discordId',
|
||||
"u"."discordId",
|
||||
'discordAvatar',
|
||||
"u"."discordAvatar",
|
||||
'customUrl',
|
||||
"u"."customUrl",
|
||||
'id',
|
||||
"u"."id"
|
||||
)
|
||||
) as "players"
|
||||
from "VideoMatch" vm
|
||||
left join "VideoMatchPlayer" vp on vm."id" = vp."videoMatchId"
|
||||
left join "User" u on vp."playerUserId" = u."id"
|
||||
where vm."videoId" = @id
|
||||
group by vm."id"
|
||||
order by vm."startsAt" asc, vp."player" asc
|
||||
`);
|
||||
|
||||
export function findVodById(id: Vod["id"]): Vod | null {
|
||||
const video = videoStm.get({ id }) as any;
|
||||
if (!video) return null;
|
||||
|
||||
const matches = videoMatchesStm.all({ id }) as any[];
|
||||
|
||||
return {
|
||||
...video,
|
||||
pov: resolvePov(matches),
|
||||
matches: matches.map(({ players: _1, playerNames: _2, ...match }) => {
|
||||
return {
|
||||
...match,
|
||||
weapons: parseDBArray(match.weapons),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function resolvePov(matches: any): Vod["pov"] {
|
||||
for (const match of matches) {
|
||||
if (parseDBArray(match.playerNames).length > 0) {
|
||||
return parseDBArray(match.playerNames)[0];
|
||||
}
|
||||
|
||||
if (parseDBArray(match.players).length > 0) {
|
||||
return parseDBArray(match.players)[0];
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
import * as R from "remeda";
|
||||
import { sql } from "~/db/sql";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import type {
|
||||
MainWeaponId,
|
||||
ModeShort,
|
||||
StageId,
|
||||
} from "~/modules/in-game-lists/types";
|
||||
import { parseDBArray, parseDBJsonArray } from "~/utils/sql";
|
||||
import { weaponIdToArrayWithAlts } from "../../../modules/in-game-lists/weapon-ids";
|
||||
import { VODS_PAGE_BATCH_SIZE } from "../vods-constants";
|
||||
import type { ListVod } from "../vods-types";
|
||||
|
||||
const query = (byUser?: true) => /* sql */ `
|
||||
select
|
||||
v."id",
|
||||
v."title",
|
||||
v."youtubeId",
|
||||
v."type",
|
||||
json_group_array("vp"."weaponSplId") as "weapons",
|
||||
json_group_array("vp"."playerName") as "playerNames",
|
||||
json_group_array(
|
||||
json_object(
|
||||
'username',
|
||||
"u"."username",
|
||||
'discordId',
|
||||
"u"."discordId",
|
||||
'discordAvatar',
|
||||
"u"."discordAvatar",
|
||||
'customUrl',
|
||||
"u"."customUrl"
|
||||
)
|
||||
) as "players"
|
||||
from "Video" v
|
||||
left join "VideoMatch" vm on v."id" = vm."videoId"
|
||||
left join "VideoMatchPlayer" vp on vm."id" = vp."videoMatchId"
|
||||
left join "User" u on vp."playerUserId" = u."id"
|
||||
where ${
|
||||
byUser
|
||||
? /* sql */ `u."id" = @userId`
|
||||
: /* sql */ `
|
||||
v."type" = coalesce(@type, v."type")
|
||||
and vm."mode" = coalesce(@mode, vm."mode")
|
||||
and vm."stageId" = coalesce(@stageId, vm."stageId")`
|
||||
}
|
||||
group by v."id"
|
||||
order by v."youtubeDate" desc
|
||||
`;
|
||||
|
||||
const stm = sql.prepare(query());
|
||||
const stmByUser = sql.prepare(query(true));
|
||||
|
||||
export function findVods({
|
||||
weapon,
|
||||
mode,
|
||||
stageId,
|
||||
type,
|
||||
userId,
|
||||
limit = VODS_PAGE_BATCH_SIZE,
|
||||
}: {
|
||||
weapon?: MainWeaponId;
|
||||
mode?: ModeShort;
|
||||
stageId?: StageId;
|
||||
type?: Tables["Video"]["type"];
|
||||
userId?: number;
|
||||
limit?: number;
|
||||
}): Array<ListVod> {
|
||||
const stmToUse = userId ? stmByUser : stm;
|
||||
|
||||
const vods = stmToUse.all({
|
||||
mode: mode ?? null,
|
||||
stageId: stageId ?? null,
|
||||
type: type ?? null,
|
||||
userId: userId ?? null,
|
||||
}) as any[];
|
||||
|
||||
const weaponIdsToFilterBy = weapon
|
||||
? weaponIdToArrayWithAlts(Number(weapon) as MainWeaponId) // TODO: fix on caller side
|
||||
: [];
|
||||
|
||||
return vods
|
||||
.filter((vod) => {
|
||||
if (weaponIdsToFilterBy.length === 0) return true;
|
||||
return parseDBArray(vod.weapons).some((weaponId: any) =>
|
||||
weaponIdsToFilterBy.includes(weaponId),
|
||||
);
|
||||
})
|
||||
.map(({ playerNames: playerNamesRaw, players: playersRaw, ...vod }) => {
|
||||
const playerNames = parseDBArray(playerNamesRaw);
|
||||
const players = parseDBJsonArray(playersRaw);
|
||||
|
||||
return {
|
||||
...vod,
|
||||
weapons: R.unique(parseDBArray(vod.weapons)),
|
||||
pov: playerNames[0] ?? players[0],
|
||||
};
|
||||
})
|
||||
.slice(0, limit);
|
||||
}
|
||||
Reference in New Issue
Block a user