diff --git a/prisma/lib/admin.ts b/prisma/lib/admin.ts index ee486b8..f579327 100644 --- a/prisma/lib/admin.ts +++ b/prisma/lib/admin.ts @@ -1,6 +1,14 @@ import { unstable_cache } from "next/cache"; import prisma from "../db"; +/** + * Retrieves all video sessions from the database, including the associated game names. + * + * This function uses `unstable_cache` to cache the results for 1 week (604800 seconds). + * The cache is tagged with "getAllSessions" for easy invalidation. + * + * @returns {Promise} A promise that resolves to an array of video sessions with game names. + */ export const getAllSessions = unstable_cache( async () => await prisma.videoSession.findMany({ diff --git a/prisma/lib/games.ts b/prisma/lib/games.ts index bdcbf7a..bac929b 100644 --- a/prisma/lib/games.ts +++ b/prisma/lib/games.ts @@ -8,9 +8,14 @@ import { getSumOfStat } from "@prisma/client/sql"; // Cache is used for deduping // unstable is used for time based caching // perks of using this method is we can invalidate certain paths. + /** - * Fetches all games from the database. - * @returns all game records including gameName and gameId + * Retrieves all games from the database. + * + * This function uses the `unstable_cache` to cache the results of the database query. + * The cache is tagged with "getAllGames" and does not revalidate. + * + * @returns {Promise} A promise that resolves to an array of games. */ export const getAllGames = unstable_cache( async () => await prisma.game.findMany(), @@ -21,13 +26,23 @@ export const getAllGames = unstable_cache( }, ); +/** + * Retrieves the sum of a specific statistic for a given player. + * + * @param playerId - The unique identifier of the player. + * @param statName - The name of the statistic to sum. + * @returns A promise that resolves to the sum of the specified statistic for the player. + */ export const getSumPerStat = async (playerId: number, statName: StatName) => await prisma.$queryRawTyped(getSumOfStat(playerId, statName)); + /** - * Fetches all sets pertaining to a game from the database. - * @param gameId id of the game record - * @returns Data about a game's sets. + * Retrieves the sets associated with a specific player in a game. + * * @deprecated + * @param {number} gameId - The unique identifier of the game. + * @returns {Promise} A promise that resolves to an array of video sessions, + * each containing the count of sets and their associated matches. */ export const getSetsPerPlayer = async (gameId: number) => await prisma.videoSession.findMany({ @@ -36,9 +51,10 @@ export const getSetsPerPlayer = async (gameId: number) => }); /** - * Fetches all records for a game including info about the winners of the session, set, and match. - * @param gameId id of the game record - * @returns all records for a game + * Retrieves the wins per player for a given game. + * + * @param {number} gameId - The unique identifier of the game. + * @returns {Promise} A promise that resolves to an object containing the sessions and their respective match winners and set winners, or null if no game is found. */ export const getWinsPerPlayer = async (gameId: number) => await prisma.game.findFirst({ @@ -60,10 +76,12 @@ export const getWinsPerPlayer = async (gameId: number) => }); /** - * Fetches all sessions for a game and includes the player sessions and matches. Useful for calculating info per match/session. - * @param gameId id of the game record - * @param statName StatName of the stat you are checking for, must end in _POS for now. - * @returns all player records with a given StatName + * Retrieves matches per game based on the provided game ID and stat name. It is useful for calculating player stats per match. + * + * @template T - The type of the stat name, extending from `StatName`. + * @param {number} gameId - The ID of the game to retrieve matches for. + * @param {StatEndsWith<"POS", T>} statName - The stat name ending with "POS" to filter player stats. + * @returns {Promise} A promise that resolves to an array of video sessions with nested sets, matches, and player sessions. */ export const getMatchesPerGame = async ( gameId: number, @@ -97,10 +115,11 @@ export const getMatchesPerGame = async ( }); /** - * Fetches all player records for a game for a given statname. - * @param gameId id of the game record - * @param statName Statname of the stat you are checking for. - * @returns Array of play records + * Retrieves statistics for each player in a specific game. The statistics are filtered by the provided statistic name. + * + * @param gameId - The unique identifier of the game. + * @param statName - The name of the statistic to retrieve. + * @returns A promise that resolves to an array of player statistics, including the player and the value of the statistic. */ export const getStatPerPlayer = async (gameId: number, statName: StatName) => await prisma.playerStat.findMany({ diff --git a/prisma/lib/members.ts b/prisma/lib/members.ts index 673b74c..2cf3281 100644 --- a/prisma/lib/members.ts +++ b/prisma/lib/members.ts @@ -1,6 +1,14 @@ import prisma from "../db"; import { unstable_cache } from "next/cache"; +/** + * Retrieves all members from the database. + * + * This function uses the `unstable_cache` to cache the results of the database query. + * The cache is tagged with "allMembers" and does not revalidate. + * + * @returns {Promise>} A promise that resolves to an array of player objects. + */ export const getAllMembers = unstable_cache( async () => { return await prisma.player.findMany(); diff --git a/prisma/lib/utils.ts b/prisma/lib/utils.ts index 5d54b31..be08d9f 100644 --- a/prisma/lib/utils.ts +++ b/prisma/lib/utils.ts @@ -1,3 +1,4 @@ +// Keep in sync with the seed script in prisma/seed.ts export enum StatNames { MarioKartPosition = "MK8_POS", MarioKartDays = "MK8_DAY", diff --git a/prisma/scripts/testbed.ts b/prisma/scripts/testbed.ts index b569037..a2d9f58 100644 --- a/prisma/scripts/testbed.ts +++ b/prisma/scripts/testbed.ts @@ -1,67 +1,9 @@ import prisma from "../db"; -import { EnrichedSession } from "../types/session"; -import { EnrichedGameSet } from "../types/gameSet"; async function main() { console.log(await getAllTimeMKRankings()); } -/// Fetched Enriched Mario Kart Session -async function getLatestMarioKartSession() { - try { - const latestMKPlayerSessions = await prisma.videoSession.findFirst({ - where: { - gameId: 1, - }, - orderBy: { - sessionId: "desc", - }, - include: { - sets: { - include: { - matches: { - include: { - playerSessions: { - include: { - playerStats: true, - }, - }, - }, - }, - }, - }, - }, - }); - if (latestMKPlayerSessions != null) { - console.log("Latest Mario Kart Session: ", [latestMKPlayerSessions]); - console.log("Set 1: ", showSetResults(latestMKPlayerSessions.sets[0])); - } - } catch (error) { - console.error("Error Fetching Latest Mario Kart Session"); - } -} - -// /** -// * Takes a mario kart set and prints out the stats for each player. -// * Format: -// * Player: playerName -// * Rankings: []number -// * @param mkSession -// */ -async function showSetResults(mkSet: EnrichedGameSet) { - for (const match of mkSet.matches) { - console.log("Looking at match: ", match); - for (const playerSession of match.playerSessions) { - console.log( - `Looking at PlayerSession: , ${playerSession} for ${playerSession.player}`, - ); - console.log( - `Player Stats: ${playerSession.playerStats.map((stat) => stat.value).join(", ")}`, - ); - } - } -} - async function getAllTimeMKRankings() { const playerStats = await prisma.playerStat.findMany({ where: { @@ -79,81 +21,6 @@ async function getAllTimeMKRankings() { console.log("Player Stats", playerStats); } -// ! TODO Can we remove this? -// async function showSetStatsByPlayerByRace(mkSession: EnrichedSession[]) { -// // Group Stats For A Set By Player -// for (const session of mkSession) { -// for (const set of session.sets) { -// console.log(`--- Set ${set.setId} ---`); -// const playerStatsMap: { [playerName: string]: string[] } = {}; - -// for (const playerSession of set.playerSessions) { -// const playerName = playerSession.player.playerName; -// if (!playerStatsMap[playerName]) { -// playerStatsMap[playerName] = []; -// } -// for (const playerStat of playerSession.playerStats) { -// playerStatsMap[playerName].push(playerStat.value); -// } -// } - -// for (const playerName in playerStatsMap) { -// console.log(`\nPlayer: ${playerName}`); -// console.log(`Placements: ${playerStatsMap[playerName].join(", ")}`); -// } -// console.log(getMK8RankingsFromSet(set)); -// } -// } -// } - -/** -/* Given a MK8 Set, return the rankings for each player and the number of points they received - * @param set - * @returns - */ -// async function getMK8RankingsFromSet(set: EnrichedGameSet) { -// const pointsMap = [6, 4, 3, 2, 1]; -// const playerPoints: { [playerName: string]: number } = { -// Mark: 0, -// Dylan: 0, -// Ben: 0, -// Lee: 0, -// Des: 0, -// }; - -// // Leaving these comments logs in for debugging purposes TODO: Remove when done -// for (const playerSession of set.playerSessions) { -// // console.log(`Looking at PlayerSession ${playerSession.playerSessionId}`); - -// const playerName = playerSession.player.playerName; - -// for (const playerStat of playerSession.playerStats) { -// // console.log(`\nLooking at statId${playerStat.statId}`); - -// const placement = parseInt(playerStat.value); -// // console.log( -// // `${playerName} placed ${placement} and received ${pointsMap[placement - 1]} points`, -// // ); -// if (placement >= 1 && placement <= 5) { -// const pointsWon = pointsMap[placement - 1]; -// // console.log(`Points Won: ${pointsWon}`); -// playerPoints[playerName] += pointsWon; -// // console.log("Player Points: ", playerPoints); -// } -// } -// // } - -// const rankings = Object.entries(playerPoints) -// .sort(([, pointsA], [, pointsB]) => pointsB - pointsA) -// .map(([playerName, points], index) => ({ -// rank: index + 1, -// playerName, -// points, -// })); - -// return rankings; -// } - main() .then(async () => { await prisma.$disconnect(); diff --git a/src/app/(routes)/(groups)/members/[slug]/page.tsx b/src/app/(routes)/(groups)/members/[slug]/page.tsx index 07b8c57..21debba 100644 --- a/src/app/(routes)/(groups)/members/[slug]/page.tsx +++ b/src/app/(routes)/(groups)/members/[slug]/page.tsx @@ -9,23 +9,10 @@ import { getAllMembers } from "../../../../../../prisma/lib/members"; export const dynamicParams = false; // true | false, -const membersObj = { - lee: "leland", - mark: "mark", - dylan: "dylan", - ben: "ben", - des: "des", - john: "john", - aff: "aff", - ippi: "ippi", -} as const; - export async function generateStaticParams() { const members = await getAllMembers(); return members.map((member) => ({ - slug: membersObj[ - member.playerName.toLowerCase() as keyof typeof membersObj - ], + slug: member.playerName.toLowerCase(), })); } diff --git a/src/app/(routes)/(groups)/members/_client/interactive-members.tsx b/src/app/(routes)/(groups)/members/_client/interactive-members.tsx index 37f6f9e..7183caa 100644 --- a/src/app/(routes)/(groups)/members/_client/interactive-members.tsx +++ b/src/app/(routes)/(groups)/members/_client/interactive-members.tsx @@ -6,6 +6,7 @@ import useViewTransition from "@/hooks/useViewTransition"; import Image from "next/image"; import { useState } from "react"; +// Deprecated export const Members3D = () => { const members = [ "https://static.wikia.nocookie.net/rdcworld1/images/f/f2/Mark-Phillips.jpg/revision/latest/thumbnail/width/360/height/450?cb=20191004005953", diff --git a/src/app/(routes)/(groups)/members/page.tsx b/src/app/(routes)/(groups)/members/page.tsx index c545432..451ae8b 100644 --- a/src/app/(routes)/(groups)/members/page.tsx +++ b/src/app/(routes)/(groups)/members/page.tsx @@ -11,14 +11,12 @@ import { HoverCardTrigger, } from "@/components/ui/hover-card"; import { RDCMembers } from "@/lib/constants"; -import { Members3D } from "./_client/interactive-members"; // TODO Revalidate Stats Once Per Week // TODO Show all button that displays all Members. Default is centered 3D circular card that pops up from the 'ground' export default async function Page() { const members = Array.from(RDCMembers.entries()); - // return ; return (

Members

diff --git a/src/app/(routes)/admin/TestAdmin.tsx b/src/app/(routes)/admin/TestAdmin.tsx deleted file mode 100644 index 523870d..0000000 --- a/src/app/(routes)/admin/TestAdmin.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import { EnrichedSession } from "../../../../prisma/types/session"; -import * as React from "react"; -import ReactDOM from "react-dom/client"; - -import "./index.css"; - -import { - createColumnHelper, - flexRender, - getCoreRowModel, - useReactTable, -} from "@tanstack/react-table"; - -type Person = { - firstName: string; - lastName: string; - age: number; - visits: number; - status: string; - progress: number; -}; - -const defaultData: Person[] = [ - { - firstName: "tanner", - lastName: "linsley", - age: 24, - visits: 100, - status: "In Relationship", - progress: 50, - }, - { - firstName: "tandy", - lastName: "miller", - age: 40, - visits: 40, - status: "Single", - progress: 80, - }, - { - firstName: "joe", - lastName: "dirte", - age: 45, - visits: 20, - status: "Complicated", - progress: 10, - }, -]; - -const columnHelper = createColumnHelper(); - -const columns = [ - columnHelper.accessor("firstName", { - cell: (info) => info.getValue(), - footer: (info) => info.column.id, - }), - columnHelper.accessor((row) => row.lastName, { - id: "lastName", - cell: (info) => {info.getValue()}, - header: () => Last Name, - footer: (info) => info.column.id, - }), - columnHelper.accessor("age", { - header: () => "Age", - cell: (info) => info.renderValue(), - footer: (info) => info.column.id, - }), - columnHelper.accessor("visits", { - header: () => Visits, - footer: (info) => info.column.id, - }), - columnHelper.accessor("status", { - header: "Status", - footer: (info) => info.column.id, - }), - columnHelper.accessor("progress", { - header: "Profile Progress", - footer: (info) => info.column.id, - }), -]; - -function App() { - const [data, _setData] = React.useState(() => [...defaultData]); - const rerender = React.useReducer(() => ({}), {})[1]; - - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - }); - - return ( -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - ))} - - ))} - - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - ))} - - ))} - - - {table.getFooterGroups().map((footerGroup) => ( - - {footerGroup.headers.map((header) => ( - - ))} - - ))} - -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), - )} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
- {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.footer, - header.getContext(), - )} -
-
- -
- ); -} diff --git a/src/app/(routes)/admin/_components/AdminDatePicker.tsx b/src/app/(routes)/admin/_components/AdminDatePicker.tsx index 9813ccd..0344611 100644 --- a/src/app/(routes)/admin/_components/AdminDatePicker.tsx +++ b/src/app/(routes)/admin/_components/AdminDatePicker.tsx @@ -1,10 +1,8 @@ "use client"; -import { zodResolver } from "@hookform/resolvers/zod"; import { format } from "date-fns"; import { CalendarIcon } from "lucide-react"; -import { useForm, useFormContext } from "react-hook-form"; -import { z } from "zod"; +import { useFormContext } from "react-hook-form"; // TODO Make sure Date is in UTC @@ -12,9 +10,7 @@ import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; import { - Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, diff --git a/src/app/(routes)/admin/_components/EntryCreatorForm.tsx b/src/app/(routes)/admin/_components/EntryCreatorForm.tsx index d9e93d5..7adb897 100644 --- a/src/app/(routes)/admin/_components/EntryCreatorForm.tsx +++ b/src/app/(routes)/admin/_components/EntryCreatorForm.tsx @@ -64,9 +64,16 @@ const EntryCreatorForm = (props: AdminFormProps) => { console.log("Errors: ", errors); /** - * Submit method called when EntryCreatorForm submit button clicked - * @param data entire "Admin" Session object constructed from values - * in EntryCreator form + * Handles the form submission for creating a new session. + * + * @param {FormValues} data - The form values to be submitted. + * @returns {Promise} A promise that resolves when the submission is complete. + * + * Logs the form data being submitted and measures the time taken for the submission process. + * Attempts to insert a new session using the provided form data. + * If an error occurs during the insertion, handles the error by either signing out the user + * or displaying an error toast message. + * If the insertion is successful, displays a success toast message and revalidates the session data. */ const onSubmit = async (data: FormValues) => { console.log("Form Data Being Submitted:", { @@ -90,11 +97,10 @@ const EntryCreatorForm = (props: AdminFormProps) => { }; /** - * Handles errors that occur during form submission. + * Handles form submission errors by logging them to the console and displaying a toast notification. * - * @param errors - An object containing the errors that occurred during form submission. + * @param {any} errors - The errors object containing details about the form submission errors. * Each key in the object corresponds to a form field, and the value is the error message for that field. - * */ const onError = (errors: any) => { console.log("Admin Form Submission Errors:", errors); diff --git a/src/app/(routes)/admin/_components/MatchManager.tsx b/src/app/(routes)/admin/_components/MatchManager.tsx index d143973..80ce0ad 100644 --- a/src/app/(routes)/admin/_components/MatchManager.tsx +++ b/src/app/(routes)/admin/_components/MatchManager.tsx @@ -25,8 +25,10 @@ const MatchManager = (props: Props) => { const statName = "MK8_POS"; // ! TODO Can we remove this /** - * Handles create new match button click. - * Creates a new child Match under parent set + * Handles the click event for creating a new match. + * Logs the players and their sessions, then appends a new match object. + * + * @returns {void} */ const handleNewMatchClick = () => { console.log("Handling New Match click", players); diff --git a/src/app/(routes)/admin/_components/PlayerStatManager.tsx b/src/app/(routes)/admin/_components/PlayerStatManager.tsx index 3ac0280..dcfd02a 100644 --- a/src/app/(routes)/admin/_components/PlayerStatManager.tsx +++ b/src/app/(routes)/admin/_components/PlayerStatManager.tsx @@ -1,13 +1,10 @@ import React, { useEffect, useState } from "react"; import { Player } from "@prisma/client"; import { useFormContext, useFieldArray } from "react-hook-form"; -import { getGameStats } from "@/app/actions/adminAction"; import { v4 as uuidv4 } from "uuid"; import { Input } from "@/components/ui/input"; -import { Skeleton } from "@/components/ui/skeleton"; import { useAdmin } from "@/lib/adminContext"; import { FormValues } from "../_utils/form-helpers"; -import { StatNames } from "../../../../../prisma/lib/utils"; interface Props { player: Player; @@ -17,8 +14,6 @@ interface Props { statName: string; } -// Need to get the stats of each game and then display appropriate input field for each stat - const PlayerStatManager = (props: Props) => { const { player, @@ -33,7 +28,6 @@ const PlayerStatManager = (props: Props) => { control, }); - // const [loading, setLoading] = useState(true); const { gameStats } = useAdmin(); // TODO: Move to PlayerSessionManager or above @@ -53,46 +47,7 @@ const PlayerStatManager = (props: Props) => { ignore = true; }; }, [gameStats, append]); - // useEffect(() => { - // console.log( - // "All Player Sessions: ", - // getValues(`sets.${setIndex}.matches.${matchIndex}.playerSessions`), - // ); - - // // Returns all the playerStats for the match - // const getPlayerSessionStats = () => { - // const values = getValues(); - // console.log(" Get PlayerSessionStats Values: ", values); - // const playerSession = - // values.sets[setIndex].matches[matchIndex].playerSessions[ - // playerSessionIndex - // ]; - // console.log("PlayerSessionStatGet: ", playerSession.playerStats); - // return playerSession.playerStats; - // }; - - // const fetchGameStats = async () => { - // console.log("Fetching game stats"); - // const existingStats = getPlayerSessionStats(); - // gameStats.forEach((stat) => { - // const statExists = existingStats.some( - // (existingStat) => existingStat.stat === stat.statName, - // ); - // // Ducttape fix to stop useEffect double render from - // // doubling playerStat fields - // if (!statExists) { - // append({ statId: uuidv4(), stat: stat.statName, statValue: "" }); - // } - // }); - // setLoading(false); - // }; - // fetchGameStats(); - // }, [append, getValues, matchIndex, playerSessionIndex, setIndex, gameStats]); - - // console.log("PlayerStatManagerFields: ", fields); - // console.log("Loading: ", loading); - // TODO Fix bug, when changing the games doesn't remove stale inputs return ( <> {fields.map((field, index: number) => { diff --git a/src/app/(routes)/admin/_components/SessionInfo.tsx b/src/app/(routes)/admin/_components/SessionInfo.tsx index 9027d05..ca41532 100644 --- a/src/app/(routes)/admin/_components/SessionInfo.tsx +++ b/src/app/(routes)/admin/_components/SessionInfo.tsx @@ -32,6 +32,22 @@ export const SessionInfo = ({ } = form; const url = form.watch("sessionUrl"); + /** + * Handles the URL update process for a session. + * + * This function performs the following steps: + * 1. Starts a transition to handle the URL update asynchronously. + * 2. Checks if the provided URL is valid and not the same as the default session URL. + * 3. If the URL is invalid, displays an error toast message. + * 4. Fetches video details using the provided URL. + * 5. If the user is not authenticated, signs out and redirects to the home page. + * 6. If there is an error fetching video details, resets the form and displays an error toast message. + * 7. If the video details are successfully fetched, updates the form with the video details and displays a success toast message. + * + * @async + * @function handleUrlUpdated + * @returns {Promise} A promise that resolves when the URL update process is complete. + */ const handleUrlUpdated = () => { startTransition(async () => { // TODO Debounce/Rate limit diff --git a/src/app/(routes)/admin/_components/SetManager.tsx b/src/app/(routes)/admin/_components/SetManager.tsx index c999767..ddaceaf 100644 --- a/src/app/(routes)/admin/_components/SetManager.tsx +++ b/src/app/(routes)/admin/_components/SetManager.tsx @@ -38,6 +38,7 @@ const SetManager = () => { const [textArea, setTextArea] = useState(fields.map(() => "")); console.log("open sets", openSets); const [highestSetId, setHighestSetId] = useState(0); + const toggleSet = (index: number) => { console.log("toggling set", index); @@ -66,6 +67,14 @@ const SetManager = () => { }); }; + /** + * Handles the addition of JSON data to a specific set. + * Parses the JSON data from the text area at the given set index, validates it, + * and updates the set with the parsed matches and set winners. + * + * @param {number} setIndex - The index of the set to which the JSON data will be added. + * @throws Will throw an error if the JSON data is invalid or not an array. + */ const handleAddJSON = (setIndex: number) => { try { const json = JSON.parse(textArea[setIndex]); @@ -73,7 +82,7 @@ const SetManager = () => { toast.error("Please upload valid json.", { richColors: true }); else { // TODO Set Values - // TODO Work In Progress. Not completed. + // TODO Work In Progress. Not completed. Awaiting the status of RDC Vision. const matches: FormValues["sets"][0]["matches"] = []; const setWinners: FormValues["sets"][0]["setWinners"] = []; const setId = randomInt(10000); diff --git a/src/app/(routes)/admin/_utils/helper-functions.ts b/src/app/(routes)/admin/_utils/helper-functions.ts index e763c35..aed1cce 100644 --- a/src/app/(routes)/admin/_utils/helper-functions.ts +++ b/src/app/(routes)/admin/_utils/helper-functions.ts @@ -1,3 +1,9 @@ +/** + * Extracts the video ID from a YouTube URL. + * + * @param url - The YouTube URL containing the video ID. + * @returns The extracted video ID. + */ export const getVideoId = (url: string) => { const queryParams = url.substring(url.indexOf("v=")); const extraParams = queryParams.indexOf("&"); diff --git a/src/app/(routes)/submission/_components/form.tsx b/src/app/(routes)/submission/_components/form.tsx index 4e35cc6..c4307f0 100644 --- a/src/app/(routes)/submission/_components/form.tsx +++ b/src/app/(routes)/submission/_components/form.tsx @@ -63,6 +63,7 @@ const formSchema = z.object({ .min(1), }); +// ! Deprecated export const SubmissionForm = () => { const [session, setSession] = useState< Awaited>["video"] | undefined diff --git a/src/app/(routes)/submission/page.tsx b/src/app/(routes)/submission/page.tsx index dd39030..069b452 100644 --- a/src/app/(routes)/submission/page.tsx +++ b/src/app/(routes)/submission/page.tsx @@ -1,7 +1,4 @@ import { H1 } from "@/components/headings"; -import { SubmissionForm } from "./_components/form"; -import { FeatureFlag } from "@/lib/featureflag"; -import { auth } from "@/auth"; import { Table, TableBody, @@ -18,22 +15,12 @@ import Link from "next/link"; import { Skeleton } from "@/components/ui/skeleton"; export default async function Page() { - const session = await auth(); return (

Sessions

}> - {/*
- - - -
*/}
); } diff --git a/src/app/actions/action.ts b/src/app/actions/action.ts index 45fd5d4..60ee8db 100644 --- a/src/app/actions/action.ts +++ b/src/app/actions/action.ts @@ -15,32 +15,40 @@ export const submitUpdates = async (props: any) => { console.log(props); }; +/** + * Updates the authentication status based on the provided session. + * If a session is provided, it signs out the user and redirects to the home page. + * If no session is provided, it initiates the sign-in process using GitHub. + * + * @param {Session | null} session - The current session object or null if no session exists. + * @returns {Promise} A promise that resolves when the authentication status is updated. + */ export const updateAuthStatus = async (session: Session | null) => { session ? await signOut({ redirectTo: "/" }) : await signIn("github"); }; -type FindManySessions = Awaited< - ReturnType ->[0]; - -type YTAPIRequestSession = Pick< - FindManySessions, - "date" | "sessionName" | "sessionUrl" -> & { - thumbnail: Thumbnail; -}; - -type GetRdcVideoDetails = Promise< - | { - video: FindManySessions | YTAPIRequestSession; - error: undefined; - } - | { - video: null; - error: string; - } ->; - +/** + * Fetches RDC video details based on the provided video ID. + * + * @param videoId - The ID of the video to fetch details for. + * @returns An object containing the video details or an error message. + * + * The function first checks if the user is authenticated. If not, it returns an error. + * It then attempts to find the video session in the database using the provided video ID. + * If the video session is not found in the database, it fetches the video details from the YouTube API. + * The function ensures that the video is uploaded by "RDC Live" and returns the video details. + * If the video session is found in the database, it returns the database record. + * + * @example + * ```typescript + * const videoDetails = await getRDCVideoDetails("someVideoId"); + * if (videoDetails.error) { + * console.error(videoDetails.error); + * } else { + * console.log(videoDetails.video); + * } + * ``` + */ export const getRDCVideoDetails = async ( videoId: string, ): GetRdcVideoDetails => { @@ -128,3 +136,25 @@ type Thumbnail = { width: number; height: number; }; + +type FindManySessions = Awaited< + ReturnType +>[0]; + +type YTAPIRequestSession = Pick< + FindManySessions, + "date" | "sessionName" | "sessionUrl" +> & { + thumbnail: Thumbnail; +}; + +type GetRdcVideoDetails = Promise< + | { + video: FindManySessions | YTAPIRequestSession; + error: undefined; + } + | { + video: null; + error: string; + } +>; diff --git a/src/app/actions/adminAction.ts b/src/app/actions/adminAction.ts index 5aee824..d023bf6 100644 --- a/src/app/actions/adminAction.ts +++ b/src/app/actions/adminAction.ts @@ -9,6 +9,13 @@ import { auth } from "@/auth"; import { errorCodes } from "@/lib/constants"; import { randomInt } from "crypto"; +/** + * Retrieves the statistics for a specified game. + * + * @param {string} gameName - The name of the game to retrieve statistics for. + * @returns {Promise} A promise that resolves to an array of game statistics. + * @throws {Error} If the game with the specified name is not found. + */ export async function getGameStats(gameName: string): Promise { console.log("Looking for gameStats for ", gameName); const game = await prisma.game.findFirst({ @@ -30,6 +37,41 @@ export async function getGameStats(gameName: string): Promise { return gameStats; } +/** + * Inserts a new session from the admin panel. + * + * @param {FormValues} session - The session details to be inserted. + * @returns {Promise<{ error: null | string }>} - A promise that resolves to an object containing an error message if any error occurs, otherwise null. + * + * @example + * const session = { + * game: "Game Name", + * sessionName: "Session Name", + * sessionUrl: "http://example.com", + * thumbnail: "http://example.com/thumbnail.jpg", + * date: "2023-10-01", + * sets: [ + * { + * setWinners: [{ playerId: 1 }], + * matches: [ + * { + * matchWinners: [{ playerId: 1 }], + * playerSessions: [ + * { + * playerId: 1, + * playerStats: [{ stat: "Score", statValue: 100 }], + * }, + * ], + * }, + * ], + * }, + * ], + * }; + * const result = await insertNewSessionFromAdmin(session); + * console.log(result); // { error: null } + * + * @throws {Error} Throws an error if an unknown error occurs. + */ export const insertNewSessionFromAdmin = async ( session: FormValues, ): Promise<{ error: null | string }> => { @@ -228,9 +270,22 @@ export const insertNewSessionFromAdmin = async ( }; /** - * A work in progress function to efficiently store form submissions. Test on a different branch. - * @param session Form values submitted by the user - * @returns A transaction that will successfully batch all of the queries together. + * Inserts a new video session into the database. + * + * @param {Object} params - The parameters for the new session. + * @param {string} params.sessionUrl - The URL of the session video. + * @param {string} params.sessionName - The name of the session. + * @param {Array} params.sets - The sets associated with the session. + * @param {string} params.game - The name of the game. + * @param {string} params.thumbnail - The thumbnail image URL for the session. + * @param {Date} params.date - The date of the session. + * @returns {Promise<{ error: string | null }>} - An object containing an error message if any, otherwise null. + * + * @throws {Error} If the user is not authenticated. + * @throws {Error} If the game is not found in the database. + * @throws {Error} If the video session already exists in the database. + * + * @async */ export const insertNewSessionV2 = async ({ sessionUrl, diff --git a/src/app/api/submit/route.ts b/src/app/api/submit/route.ts index c999c93..2fdcdff 100644 --- a/src/app/api/submit/route.ts +++ b/src/app/api/submit/route.ts @@ -3,8 +3,23 @@ import { auth } from "@/auth"; import { revalidateTag } from "next/cache"; import { NextRequest, NextResponse } from "next/server"; -// TODO Throwing build error -export async function POST(request: NextRequest) { +/** + * Handles the POST request to submit a new session. + * + * @param {NextRequest} request - The incoming request object. + * @returns {Promise} The response object. + * + * The function performs the following steps: + * 1. Authenticates the user session. + * 2. If the session is not authenticated, returns a 401 Unauthorized response. + * 3. If the environment is development: + * - Parses the request body. + * - Attempts to insert a new session from the admin. + * - If successful, revalidates the "getAllSessions" tag and returns a 200 Success response. + * - If an error occurs, returns a 400 Bad Request response with the error message. + * 4. If the environment is not development, returns a 404 response indicating the need to be in development mode. + */ +export async function POST(request: NextRequest): Promise { const session = await auth(); if (!session) { return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); @@ -22,5 +37,8 @@ export async function POST(request: NextRequest) { } } - return NextResponse.json({ message: "Need to be on dev" }, { status: 404 }); + return NextResponse.json( + { message: "This endpoint is only available in development mode" }, + { status: 404 }, + ); } diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index 24699b5..04660ed 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -2,7 +2,14 @@ import { H2 } from "@/components/headings"; import { Skeleton } from "@/components/ui/skeleton"; import { Suspense } from "react"; -const Loader = () => ; +/** + * A React component that renders a "Not Found" page with a custom message and an embedded YouTube video. + * The message can be customized based on the presence of an error in the search parameters. + * + * @param {Object} props - The component props. + * @param {Promise<{ [key: string]: string | string[] | undefined }>} props.searchParams - A promise that resolves to an object containing search parameters. + * @returns {JSX.Element} The rendered "Not Found" page. + */ export default async function NotFound({ searchParams, }: { @@ -20,7 +27,7 @@ export default async function NotFound({
{!error &&

Page Not Found

}

{msg}

- }> + }>