@@ -9,8 +9,12 @@ import {
99 checkAuthorization ,
1010} from "../authorization.server" ;
1111import { logger } from "../logger.server" ;
12+ import {
13+ authenticateApiRequestWithPersonalAccessToken ,
14+ PersonalAccessTokenAuthenticationResult ,
15+ } from "../personalAccessToken.server" ;
1216
13- type RouteBuilderOptions <
17+ type ApiKeyRouteBuilderOptions <
1418 TParamsSchema extends z . AnyZodObject | undefined = undefined ,
1519 TSearchParamsSchema extends z . AnyZodObject | undefined = undefined
1620> = {
@@ -30,7 +34,7 @@ type RouteBuilderOptions<
3034 } ;
3135} ;
3236
33- type HandlerFunction <
37+ type ApiKeyHandlerFunction <
3438 TParamsSchema extends z . AnyZodObject | undefined ,
3539 TSearchParamsSchema extends z . AnyZodObject | undefined
3640> = ( args : {
@@ -46,8 +50,8 @@ export function createLoaderApiRoute<
4650 TParamsSchema extends z . AnyZodObject | undefined = undefined ,
4751 TSearchParamsSchema extends z . AnyZodObject | undefined = undefined
4852> (
49- options : RouteBuilderOptions < TParamsSchema , TSearchParamsSchema > ,
50- handler : HandlerFunction < TParamsSchema , TSearchParamsSchema >
53+ options : ApiKeyRouteBuilderOptions < TParamsSchema , TSearchParamsSchema > ,
54+ handler : ApiKeyHandlerFunction < TParamsSchema , TSearchParamsSchema >
5155) {
5256 return async function loader ( { request, params } : LoaderFunctionArgs ) {
5357 const {
@@ -147,6 +151,110 @@ export function createLoaderApiRoute<
147151 } ;
148152}
149153
154+ type PATRouteBuilderOptions <
155+ TParamsSchema extends z . AnyZodObject | undefined = undefined ,
156+ TSearchParamsSchema extends z . AnyZodObject | undefined = undefined
157+ > = {
158+ params ?: TParamsSchema ;
159+ searchParams ?: TSearchParamsSchema ;
160+ corsStrategy ?: "all" | "none" ;
161+ } ;
162+
163+ type PATHandlerFunction <
164+ TParamsSchema extends z . AnyZodObject | undefined ,
165+ TSearchParamsSchema extends z . AnyZodObject | undefined
166+ > = ( args : {
167+ params : TParamsSchema extends z . AnyZodObject ? z . infer < TParamsSchema > : undefined ;
168+ searchParams : TSearchParamsSchema extends z . AnyZodObject
169+ ? z . infer < TSearchParamsSchema >
170+ : undefined ;
171+ authentication : PersonalAccessTokenAuthenticationResult ;
172+ request : Request ;
173+ } ) => Promise < Response > ;
174+
175+ export function createLoaderPATApiRoute <
176+ TParamsSchema extends z . AnyZodObject | undefined = undefined ,
177+ TSearchParamsSchema extends z . AnyZodObject | undefined = undefined
178+ > (
179+ options : PATRouteBuilderOptions < TParamsSchema , TSearchParamsSchema > ,
180+ handler : PATHandlerFunction < TParamsSchema , TSearchParamsSchema >
181+ ) {
182+ return async function loader ( { request, params } : LoaderFunctionArgs ) {
183+ const {
184+ params : paramsSchema ,
185+ searchParams : searchParamsSchema ,
186+ corsStrategy = "none" ,
187+ } = options ;
188+
189+ if ( corsStrategy !== "none" && request . method . toUpperCase ( ) === "OPTIONS" ) {
190+ return apiCors ( request , json ( { } ) ) ;
191+ }
192+
193+ const authenticationResult = await authenticateApiRequestWithPersonalAccessToken ( request ) ;
194+
195+ if ( ! authenticationResult ) {
196+ return wrapResponse (
197+ request ,
198+ json ( { error : "Invalid or Missing API key" } , { status : 401 } ) ,
199+ corsStrategy !== "none"
200+ ) ;
201+ }
202+
203+ let parsedParams : any = undefined ;
204+ if ( paramsSchema ) {
205+ const parsed = paramsSchema . safeParse ( params ) ;
206+ if ( ! parsed . success ) {
207+ return wrapResponse (
208+ request ,
209+ json (
210+ { error : "Params Error" , details : fromZodError ( parsed . error ) . details } ,
211+ { status : 400 }
212+ ) ,
213+ corsStrategy !== "none"
214+ ) ;
215+ }
216+ parsedParams = parsed . data ;
217+ }
218+
219+ let parsedSearchParams : any = undefined ;
220+ if ( searchParamsSchema ) {
221+ const searchParams = Object . fromEntries ( new URL ( request . url ) . searchParams ) ;
222+ const parsed = searchParamsSchema . safeParse ( searchParams ) ;
223+ if ( ! parsed . success ) {
224+ return wrapResponse (
225+ request ,
226+ json (
227+ { error : "Query Error" , details : fromZodError ( parsed . error ) . details } ,
228+ { status : 400 }
229+ ) ,
230+ corsStrategy !== "none"
231+ ) ;
232+ }
233+ parsedSearchParams = parsed . data ;
234+ }
235+
236+ try {
237+ const result = await handler ( {
238+ params : parsedParams ,
239+ searchParams : parsedSearchParams ,
240+ authentication : authenticationResult ,
241+ request,
242+ } ) ;
243+ return wrapResponse ( request , result , corsStrategy !== "none" ) ;
244+ } catch ( error ) {
245+ console . error ( "Error in API route:" , error ) ;
246+ if ( error instanceof Response ) {
247+ return wrapResponse ( request , error , corsStrategy !== "none" ) ;
248+ }
249+ return wrapResponse (
250+ request ,
251+ json ( { error : "Internal Server Error" } , { status : 500 } ) ,
252+ corsStrategy !== "none"
253+ ) ;
254+ }
255+ } ;
256+ }
257+
150258function wrapResponse ( request : Request , response : Response , useCors : boolean ) {
151259 return useCors ? apiCors ( request , response ) : response ;
152260}
0 commit comments