From 578c66ac457882e6117b82b9da55ef93c78e3c3e Mon Sep 17 00:00:00 2001 From: renatodellosso Date: Thu, 19 Dec 2024 21:46:33 -0500 Subject: [PATCH] Started making NextApiAdapter --- lib/api/AccessLevels.ts | 28 +++++++-------- lib/api/ApiLib.ts | 73 +++++++++++++++++++------------------- lib/api/ClientApi.ts | 7 ++-- lib/api/Errors.ts | 2 +- lib/api/NextApiAdapter.ts | 63 ++++++++++++++++++++++++++++++++ lib/api/ServerApi.ts | 2 +- lib/testutils/TestUtils.ts | 2 +- 7 files changed, 122 insertions(+), 55 deletions(-) create mode 100644 lib/api/NextApiAdapter.ts diff --git a/lib/api/AccessLevels.ts b/lib/api/AccessLevels.ts index 376978e0..144dd4af 100644 --- a/lib/api/AccessLevels.ts +++ b/lib/api/AccessLevels.ts @@ -38,7 +38,7 @@ namespace AccessLevels { export async function IfSignedIn( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise }: UserAndDb, ) { return { @@ -49,7 +49,7 @@ namespace AccessLevels { export async function IfDeveloper( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise }: UserAndDb, ) { const user = await userPromise; @@ -58,7 +58,7 @@ namespace AccessLevels { export async function IfOnTeam( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise, db }: UserAndDb, teamId: string, ) { @@ -82,7 +82,7 @@ namespace AccessLevels { export async function IfTeamOwner( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise, db }: UserAndDb, teamId: string, ) { @@ -106,7 +106,7 @@ namespace AccessLevels { export async function IfCompOwner( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise, db }: UserAndDb, compId: string, ) { @@ -138,7 +138,7 @@ namespace AccessLevels { export async function IfSeasonOwner( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise, db }: UserAndDb, seasonId: string, ) { @@ -167,7 +167,7 @@ namespace AccessLevels { export async function IfMatchOwner( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise, db }: UserAndDb, matchId: string, ) { @@ -201,7 +201,7 @@ namespace AccessLevels { export async function IfReportOwner( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise, db }: UserAndDb, reportId: string, ) { @@ -230,7 +230,7 @@ namespace AccessLevels { export async function IfOnTeamThatOwnsComp( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise, db }: UserAndDb, compId: string, ) { @@ -262,7 +262,7 @@ namespace AccessLevels { export async function IfOnTeamThatOwnsMatch( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise, db }: UserAndDb, matchId: string, ) { @@ -296,7 +296,7 @@ namespace AccessLevels { export async function IfOnTeamThatOwnsPitReport( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise, db }: UserAndDb, pitReportId: string, ) { @@ -333,7 +333,7 @@ namespace AccessLevels { export async function IfOnTeamThatOwnsReport( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise, db }: UserAndDb, reportId: string, ) { @@ -362,7 +362,7 @@ namespace AccessLevels { export async function IfOnTeamThatOwnsSubjectiveReport( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise, db }: UserAndDb, reportId: string, ) { @@ -394,7 +394,7 @@ namespace AccessLevels { export async function IfOnTeamThatOwnsPicklist( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, { userPromise, db }: UserAndDb, picklistId: string, ) { diff --git a/lib/api/ApiLib.ts b/lib/api/ApiLib.ts index 5ae15f78..95c98473 100644 --- a/lib/api/ApiLib.ts +++ b/lib/api/ApiLib.ts @@ -1,4 +1,4 @@ -import { NextApiRequest, NextApiResponse } from "next"; +import { NextApiResponse } from "next"; import { OmitCallSignature } from "@/lib/Types"; import toast from "react-hot-toast"; @@ -59,23 +59,15 @@ namespace ApiLib { } } - export class ApiResponse { - constructor(public innerRes: NextApiResponse) {} - - send(data: TSend | Errors.ErrorType) { - this.innerRes.send(data); - return this; - } - - status(code: number) { - this.innerRes.status(code); - return this; - } + export interface HttpRequest { + url?: string; + body: any; + } - error(code: number, message: string) { - this.innerRes.status(code).send({ error: message }); - return this; - } + export interface ApiResponse { + send(data: TSend | Errors.ErrorType): ApiResponse; + status(code: number): ApiResponse; + error(code: number, message: string): ApiResponse; } export type Route< @@ -83,14 +75,16 @@ namespace ApiLib { TReturn, TDependencies, TDataFetchedDuringAuth, + TRequest extends HttpRequest = HttpRequest, + TResponse extends ApiResponse = ApiResponse, > = { subUrl: string; (...args: TArgs): Promise; isAuthorized: ( - req: NextApiRequest, - res: ApiResponse, + req: TRequest, + res: TResponse, deps: TDependencies, args: TArgs, ) => Promise<{ @@ -98,8 +92,8 @@ namespace ApiLib { authData: TDataFetchedDuringAuth | undefined; }>; handler: ( - req: NextApiRequest, - res: ApiResponse, + req: TRequest, + res: TResponse, deps: TDependencies, authData: TDataFetchedDuringAuth, args: TArgs, @@ -149,15 +143,16 @@ namespace ApiLib { TReturn, TDependencies, TFetchedDuringAuth, + TRequest extends HttpRequest = HttpRequest, >( server: Omit< OmitCallSignature< - Route + Route >, "subUrl" >, clientHandler?: (...args: any) => Promise, - ): Route { + ): Route { return Object.assign( clientHandler ?? { subUrl: "newRoute" }, server, @@ -170,16 +165,17 @@ namespace ApiLib { | Route; }; - export abstract class ApiTemplate { - // [route: string]: any; - + export abstract class ApiTemplate< + TDependencies, + TRequest extends HttpRequest = HttpRequest, + > { private initSegment(segment: Segment, subUrl: string) { for (const [key, value] of Object.entries(segment)) { if (typeof value === "function") { value.subUrl = subUrl + "/" + key; } else if ( - (value as unknown as Route).subUrl === - "newRoute" + (value as unknown as Route) + .subUrl === "newRoute" ) { const route = value as unknown as Route; route.subUrl = subUrl + "/" + key; @@ -214,15 +210,19 @@ namespace ApiLib { None, } - export abstract class ServerApi { + export abstract class ServerApi< + TDependencies, + TRequest extends HttpRequest = HttpRequest, + TResponse extends ApiResponse = ApiResponse, + > { constructor( private api: ApiTemplate, private urlPrefix: string, private errorLogMode: ErrorLogMode = ErrorLogMode.Log, ) {} - async handle(req: NextApiRequest, rawRes: NextApiResponse) { - const res = new ApiResponse(rawRes); + async handle(req: TRequest, rawRes: any) { + const res = this.parseRawResponse(rawRes); if (!req.url) { throw new Errors.InvalidRequestError(res); @@ -266,14 +266,15 @@ namespace ApiLib { return; } - new Errors.InternalServerError(new ApiResponse(rawRes)); + new Errors.InternalServerError(res); } } - abstract getDependencies( - req: NextApiRequest, - res: ApiResponse, - ): TDependencies; + protected parseRawResponse(rawRes: any): TResponse { + return rawRes; + } + + abstract getDependencies(req: TRequest, res: TResponse): TDependencies; } } diff --git a/lib/api/ClientApi.ts b/lib/api/ClientApi.ts index cdfa543e..f8e5bcb8 100644 --- a/lib/api/ClientApi.ts +++ b/lib/api/ClientApi.ts @@ -41,14 +41,17 @@ import { import { games } from "../games"; import { Statbotics } from "../Statbotics"; import { TheBlueAlliance } from "../TheBlueAlliance"; -import { request } from "http"; import { SlackNotLinkedError } from "./Errors"; import { _id } from "@next-auth/mongodb-adapter"; +import { NextApiRequest } from "next"; /** * @tested_by tests/lib/api/ClientApi.test.ts */ -export default class ClientApi extends ApiLib.ApiTemplate { +export default class ClientApi extends ApiLib.ApiTemplate< + ApiDependencies, + NextApiRequest +> { constructor() { super(false); this.init(); diff --git a/lib/api/Errors.ts b/lib/api/Errors.ts index ce4ebd3d..12ae31f8 100644 --- a/lib/api/Errors.ts +++ b/lib/api/Errors.ts @@ -1,7 +1,7 @@ import ApiLib from "./ApiLib"; export class SlackNotLinkedError extends ApiLib.Errors.Error { - constructor(res: ApiLib.ApiResponse) { + constructor(res: ApiLib.NextResponse) { super(res, 400, "Team has not provided a Slack webhook"); } } diff --git a/lib/api/NextApiAdapter.ts b/lib/api/NextApiAdapter.ts new file mode 100644 index 00000000..7d1a9f8b --- /dev/null +++ b/lib/api/NextApiAdapter.ts @@ -0,0 +1,63 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import ApiLib from "./ApiLib"; +import { OmitCallSignature } from "../Types"; + +namespace NextApiAdapter { + export class NextResponse implements ApiLib.ApiResponse { + constructor(public innerRes: NextApiResponse) {} + + send(data: TSend | ApiLib.Errors.ErrorType) { + this.innerRes.send(data); + return this; + } + + status(code: number) { + this.innerRes.status(code); + return this; + } + + error(code: number, message: string) { + this.innerRes.status(code).send({ error: message }); + return this; + } + } + + export function createRoute< + TArgs extends Array, + TReturn, + TDependencies, + TFetchedDuringAuth, + >( + server: Omit< + OmitCallSignature< + ApiLib.Route< + TArgs, + TReturn, + TDependencies, + TFetchedDuringAuth, + NextApiRequest + > + >, + "subUrl" + >, + clientHandler?: (...args: any) => Promise, + ): ApiLib.Route< + TArgs, + TReturn, + TDependencies, + TFetchedDuringAuth, + NextApiRequest + > { + return ApiLib.createRoute(server, clientHandler); + } + + export abstract class ServerApi extends ApiLib.ServerApi< + TDependencies, + NextApiRequest, + NextResponse + > { + protected parseRawResponse(rawRes: any): NextResponse { + return new NextResponse(rawRes); + } + } +} diff --git a/lib/api/ServerApi.ts b/lib/api/ServerApi.ts index eb07c4aa..abda70ad 100644 --- a/lib/api/ServerApi.ts +++ b/lib/api/ServerApi.ts @@ -18,7 +18,7 @@ export default class ServerApi extends ApiLib.ServerApi { getDependencies( req: NextApiRequest, - res: ApiLib.ApiResponse, + res: ApiLib.NextResponse, ): ApiDependencies { return { db: getDatabase(), diff --git a/lib/testutils/TestUtils.ts b/lib/testutils/TestUtils.ts index 38dfc841..bbb7bc2d 100644 --- a/lib/testutils/TestUtils.ts +++ b/lib/testutils/TestUtils.ts @@ -9,7 +9,7 @@ import InMemoryDbInterface from "../client/dbinterfaces/InMemoryDbInterface"; import { ResendInterface } from "../ResendUtils"; import { SlackInterface } from "../SlackClient"; -export class TestRes extends ApiLib.ApiResponse { +export class TestRes extends ApiLib.NextResponse { status = jest.fn((code) => this); send = jest.fn((obj) => this); error = jest.fn((code, message) => {