From ce551881e2105858e69444645dc26f8c028cd247 Mon Sep 17 00:00:00 2001 From: YoniKiriaty Date: Wed, 4 Feb 2026 20:13:45 +0200 Subject: [PATCH 1/4] Yoni - flattened function --- apps/scouting/backend/src/routes/tba.ts | 64 +++++++++++++++---- packages/array-functions/index.ts | 5 ++ .../scouting_types/tba/ScoreBreakdown2026.ts | 2 + packages/scouting_types/tba/TBAMatch.ts | 1 + 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/apps/scouting/backend/src/routes/tba.ts b/apps/scouting/backend/src/routes/tba.ts index 418317d..b1660da 100644 --- a/apps/scouting/backend/src/routes/tba.ts +++ b/apps/scouting/backend/src/routes/tba.ts @@ -9,6 +9,7 @@ import { } from "../middleware/verification"; import { matchesProps, + type ScoreBreakdown2026, scoreBreakdown2026, tbaMatch, } from "@repo/scouting_types"; @@ -20,17 +21,29 @@ import { type TaskEither, fromEither, tryCatch, + map, } 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,23 +63,54 @@ 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 getCurrentMatches = flow( + getCollection, + flatMap((collection) => + tryCatch(collection.find().toArray, (error) => ({ + reason: `Error getting from collection ${String(error)}`, + status: StatusCodes.BAD_REQUEST, + })), + ), +); 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({}))), + pipe( + getCurrentMatches(), + 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), + map((fetchedMatches) => { + insertMatches(fetchedMatches); + return fetchedMatches; + }), + ), + () => fromEither(right(currentMatches)), + ), ), ), fold( @@ -75,9 +119,7 @@ tbaRouter.post("/matches", async (req, res) => { 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; From 6bb26e0de571c3c6618302bc978fb6a8bdc5752d Mon Sep 17 00:00:00 2001 From: YoniKiriaty Date: Wed, 4 Feb 2026 20:21:57 +0200 Subject: [PATCH 2/4] Yoni - split --- apps/scouting/backend/src/routes/tba.ts | 55 ++++++++++++++----------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/apps/scouting/backend/src/routes/tba.ts b/apps/scouting/backend/src/routes/tba.ts index b1660da..1d47eb6 100644 --- a/apps/scouting/backend/src/routes/tba.ts +++ b/apps/scouting/backend/src/routes/tba.ts @@ -9,9 +9,9 @@ import { } from "../middleware/verification"; import { matchesProps, - type ScoreBreakdown2026, scoreBreakdown2026, tbaMatch, + type TBAMatchesProps, } from "@repo/scouting_types"; import { right } from "fp-ts/lib/Either"; import { StatusCodes } from "http-status-codes"; @@ -76,7 +76,7 @@ const insertMatches = (matches: TBAMatches) => getCollection(), map((collection) => collection.insertMany(matches)), ); -const getCurrentMatches = flow( +const getStoredMatches = flow( getCollection, flatMap((collection) => tryCatch(collection.find().toArray, (error) => ({ @@ -85,34 +85,39 @@ const getCurrentMatches = flow( })), ), ); + +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), + map((fetchedMatches) => { + insertMatches(fetchedMatches); + return fetchedMatches; + }), + ), + () => fromEither(right(currentMatches)), + ), + ), + ), +); + tbaRouter.post("/matches", async (req, res) => { await pipe( right(req), createBodyVerificationPipe(matchesProps), fromEither, - flatMap((body) => - pipe( - getCurrentMatches(), - 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), - map((fetchedMatches) => { - insertMatches(fetchedMatches); - return fetchedMatches; - }), - ), - () => fromEither(right(currentMatches)), - ), - ), - ), + getMatches, fold( (error) => () => new Promise((resolve) => { From 63c669139259fd40d85be17ae883bae5dddc5827 Mon Sep 17 00:00:00 2001 From: YoniKiriaty Date: Wed, 4 Feb 2026 21:45:49 +0200 Subject: [PATCH 3/4] Yoni - added chain first --- apps/scouting/backend/src/routes/tba.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/scouting/backend/src/routes/tba.ts b/apps/scouting/backend/src/routes/tba.ts index 1d47eb6..7b61357 100644 --- a/apps/scouting/backend/src/routes/tba.ts +++ b/apps/scouting/backend/src/routes/tba.ts @@ -22,6 +22,7 @@ import { fromEither, tryCatch, map, + chainFirstTaskK, } from "fp-ts/lib/TaskEither"; import { flow, pipe } from "fp-ts/lib/function"; import { map as taskMap } from "fp-ts/lib/Task"; @@ -30,6 +31,7 @@ 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"; +import { chainFirstIOK } from "fp-ts/lib/IOEither"; export const tbaRouter = Router(); @@ -101,10 +103,10 @@ const getMatches = flow( () => pipe( fetchTba(`/event/${body.event}/matches`, tbaMatches), - map((fetchedMatches) => { - insertMatches(fetchedMatches); - return fetchedMatches; - }), + chainFirstTaskK( + (fetchedMatches) => async () => + Promise.resolve(insertMatches(fetchedMatches)), + ), ), () => fromEither(right(currentMatches)), ), From 7d92a8b8615c887bac80a65a6002a9a8533a4326 Mon Sep 17 00:00:00 2001 From: YoniKiriaty Date: Wed, 4 Feb 2026 21:46:02 +0200 Subject: [PATCH 4/4] Yoni - removed useless import --- apps/scouting/backend/src/routes/tba.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/scouting/backend/src/routes/tba.ts b/apps/scouting/backend/src/routes/tba.ts index 7b61357..8e796e4 100644 --- a/apps/scouting/backend/src/routes/tba.ts +++ b/apps/scouting/backend/src/routes/tba.ts @@ -31,7 +31,6 @@ 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"; -import { chainFirstIOK } from "fp-ts/lib/IOEither"; export const tbaRouter = Router();