diff --git a/apps/scouting/backend/src/routes/tba.ts b/apps/scouting/backend/src/routes/tba.ts index 418317d..8e796e4 100644 --- a/apps/scouting/backend/src/routes/tba.ts +++ b/apps/scouting/backend/src/routes/tba.ts @@ -11,6 +11,7 @@ import { matchesProps, scoreBreakdown2026, tbaMatch, + type TBAMatchesProps, } from "@repo/scouting_types"; import { right } from "fp-ts/lib/Either"; import { StatusCodes } from "http-status-codes"; @@ -20,17 +21,30 @@ import { type TaskEither, fromEither, tryCatch, + map, + chainFirstTaskK, } from "fp-ts/lib/TaskEither"; -import { pipe } from "fp-ts/lib/function"; -import { map } from "fp-ts/lib/Task"; +import { flow, pipe } from "fp-ts/lib/function"; +import { map as taskMap } from "fp-ts/lib/Task"; import * as t from "io-ts"; import type { Type } from "io-ts"; +import { getDb } from "../middleware/db"; +import { getMax } from "@repo/array-functions"; +import { fold as booleanFold } from "fp-ts/boolean"; export const tbaRouter = Router(); const TBA_KEY = process.env.TBA_API_KEY ?? "yourtbakey"; const TBA_URL = "https://www.thebluealliance.com/api/v3"; +const tbaMatches = t.array(tbaMatch(scoreBreakdown2026, t.type({}))); +type TBAMatches = t.TypeOf; + +const getCollection = flow( + getDb, + map((db) => db.collection("tba")), +); + const fetchTba = ( route: string, typeToCheck: Type, @@ -50,34 +64,68 @@ const fetchTba = ( reason: `Error Fetching From TBA: error ${error}`, }), ), - map( + taskMap( createTypeCheckingEndpointFlow(typeToCheck, (errors) => ({ status: StatusCodes.INTERNAL_SERVER_ERROR, reason: `Recieved incorrect response from the TBA. error: ${errors}`, - })) - ) + })), + ), ) satisfies TaskEither; +const insertMatches = (matches: TBAMatches) => + pipe( + getCollection(), + map((collection) => collection.insertMany(matches)), + ); +const getStoredMatches = flow( + getCollection, + flatMap((collection) => + tryCatch(collection.find().toArray, (error) => ({ + reason: `Error getting from collection ${String(error)}`, + status: StatusCodes.BAD_REQUEST, + })), + ), +); + +const getMatches = flow( + flatMap((body: TBAMatchesProps) => + pipe( + getStoredMatches(), + map((currentMatches) => ({ currentMatches, body })), + ), + ), + flatMap(({ currentMatches, body }) => + pipe( + getMax(currentMatches, (match) => match.match_number).match_number < + body.maxMatch, + booleanFold( + () => + pipe( + fetchTba(`/event/${body.event}/matches`, tbaMatches), + chainFirstTaskK( + (fetchedMatches) => async () => + Promise.resolve(insertMatches(fetchedMatches)), + ), + ), + () => fromEither(right(currentMatches)), + ), + ), + ), +); + tbaRouter.post("/matches", async (req, res) => { await pipe( right(req), createBodyVerificationPipe(matchesProps), fromEither, - flatMap((body) => - fetchTba( - `/event/${body.event}/matches`, - t.array(tbaMatch(scoreBreakdown2026, t.type({}))), - ), - ), + getMatches, fold( (error) => () => new Promise((resolve) => { resolve(res.status(error.status).send(error.reason)); }), (matches) => () => - new Promise((resolve) => { - resolve(res.status(StatusCodes.OK).json({ matches })); - }), + Promise.resolve(res.status(StatusCodes.OK).json({ matches })), ), )(); }); diff --git a/packages/array-functions/index.ts b/packages/array-functions/index.ts index 520bc90..645cd90 100644 --- a/packages/array-functions/index.ts +++ b/packages/array-functions/index.ts @@ -7,6 +7,11 @@ export const calculateSum = ( ): number => arr.reduce((sum, value) => sum + transformation(value), startingSumValue); +export const getMax = (arr: T[], transformation: (value: T) => number): T => + arr + .map((item) => ({ value: transformation(item), item })) + .reduce((max, curr) => (curr.value > max.value ? curr : max)).item; + const FIRST_ELEMENT_ID = 0; const LAST_ELEMENT_BACKWARDS_INDEX = 1; export const firstElement = (arr: T[]): T => arr[FIRST_ELEMENT_ID]; diff --git a/packages/scouting_types/tba/ScoreBreakdown2026.ts b/packages/scouting_types/tba/ScoreBreakdown2026.ts index 576df21..7f4562c 100644 --- a/packages/scouting_types/tba/ScoreBreakdown2026.ts +++ b/packages/scouting_types/tba/ScoreBreakdown2026.ts @@ -56,3 +56,5 @@ export const scoreBreakdown2026 = t.type({ totalTowerPoints: t.number, totalPoints: t.number, }); + +export type ScoreBreakdown2026 = t.TypeOf; diff --git a/packages/scouting_types/tba/TBAMatch.ts b/packages/scouting_types/tba/TBAMatch.ts index ba5ac6b..bcf0c3a 100644 --- a/packages/scouting_types/tba/TBAMatch.ts +++ b/packages/scouting_types/tba/TBAMatch.ts @@ -3,6 +3,7 @@ import * as t from "io-ts"; export const matchesProps = t.type({ event: t.string, + maxMatch: t.number }); export type TBAMatchesProps = t.TypeOf;