@@ -11,6 +11,12 @@ type GithubEnv = {
1111 TURNSTILE_SECRET_KEY : string ;
1212} ;
1313
14+ type ErrorResponse = {
15+ error : string ;
16+ code ?: string ;
17+ details ?: string ;
18+ } ;
19+
1420type GithubIssue = {
1521 url : string ;
1622 repository_url : string ;
@@ -169,35 +175,129 @@ const app = new Hono()
169175 TURNSTILE_SECRET_KEY ,
170176 ) ;
171177 if ( ! isValid ) {
172- return c . json ( { error : "Invalid Turnstile verification" } , 400 ) ;
178+ return c . json (
179+ {
180+ error : "Invalid Turnstile verification" ,
181+ code : "TURNSTILE_VERIFICATION_FAILED" ,
182+ details : "Please refresh the page and try again" ,
183+ } as ErrorResponse ,
184+ 400 ,
185+ ) ;
173186 }
174187 }
175188
189+ // Validate input
190+ if ( ! title || title . length < 7 ) {
191+ return c . json (
192+ {
193+ error : "Title is required and must be at least 7 characters long" ,
194+ code : "INVALID_TITLE" ,
195+ } as ErrorResponse ,
196+ 400 ,
197+ ) ;
198+ }
199+
200+ if ( ! body || body . trim ( ) . length < 10 ) {
201+ return c . json (
202+ {
203+ error :
204+ "Description is required and must be at least 10 characters long" ,
205+ code : "INVALID_DESCRIPTION" ,
206+ } as ErrorResponse ,
207+ 400 ,
208+ ) ;
209+ }
210+
176211 // base64 encoded private key to utf8, not using buffer
177212 const privateKey = atob ( GITHUB_APP_PRIVATE_KEY ) ;
178213
179- const accessToken = await getInstallationAccessToken (
180- GITHUB_CLIENT_ID ,
181- privateKey ,
182- GITHUB_INSTALLATION_ID ,
183- ) ;
214+ let accessToken : string ;
215+ try {
216+ accessToken = await getInstallationAccessToken (
217+ GITHUB_CLIENT_ID ,
218+ privateKey ,
219+ GITHUB_INSTALLATION_ID ,
220+ ) ;
221+ } catch ( error ) {
222+ console . error ( "Failed to get GitHub access token:" , error ) ;
223+ return c . json (
224+ {
225+ error : "Failed to authenticate with GitHub" ,
226+ code : "GITHUB_AUTH_FAILED" ,
227+ details : "Please try again later" ,
228+ } as ErrorResponse ,
229+ 500 ,
230+ ) ;
231+ }
184232 const repoOwner = "nthumodifications" ;
185233 const repoName = "courseweb" ;
186234
187- const response = await fetch (
188- `https://api.github.com/repos/${ repoOwner } /${ repoName } /issues` ,
189- {
190- method : "POST" ,
191- headers : {
192- "User-Agent" : "nthumods-app" ,
193- Authorization : `token ${ accessToken } ` ,
194- Accept : "application/vnd.github.v3+json" ,
235+ try {
236+ const response = await fetch (
237+ `https://api.github.com/repos/${ repoOwner } /${ repoName } /issues` ,
238+ {
239+ method : "POST" ,
240+ headers : {
241+ "User-Agent" : "nthumods-app" ,
242+ Authorization : `token ${ accessToken } ` ,
243+ Accept : "application/vnd.github.v3+json" ,
244+ } ,
245+ body : JSON . stringify ( { title, body, labels } ) ,
195246 } ,
196- body : JSON . stringify ( { title, body, labels } ) ,
197- } ,
198- ) ;
199- const data = ( await response . json ( ) ) as GithubIssue ;
200- return c . json ( data ) ;
247+ ) ;
248+
249+ if ( ! response . ok ) {
250+ const errorText = await response . text ( ) ;
251+ console . error ( `GitHub API error: ${ response . status } ${ errorText } ` ) ;
252+
253+ let errorResponse : ErrorResponse ;
254+
255+ switch ( response . status ) {
256+ case 401 :
257+ errorResponse = {
258+ error : "Authentication failed with GitHub" ,
259+ code : "GITHUB_AUTH_INVALID" ,
260+ details : "Please refresh and try again" ,
261+ } ;
262+ break ;
263+ case 403 :
264+ errorResponse = {
265+ error : "Access denied by GitHub" ,
266+ code : "GITHUB_ACCESS_DENIED" ,
267+ details : "Rate limit exceeded or insufficient permissions" ,
268+ } ;
269+ break ;
270+ case 422 :
271+ errorResponse = {
272+ error : "Invalid issue data" ,
273+ code : "GITHUB_VALIDATION_ERROR" ,
274+ details : "The issue data was rejected by GitHub" ,
275+ } ;
276+ break ;
277+ default :
278+ errorResponse = {
279+ error : "Failed to create GitHub issue" ,
280+ code : "GITHUB_API_ERROR" ,
281+ details : `HTTP ${ response . status } : ${ response . statusText } ` ,
282+ } ;
283+ }
284+
285+ return c . json ( errorResponse , response . status as any ) ;
286+ }
287+
288+ const data = ( await response . json ( ) ) as GithubIssue ;
289+ return c . json ( data ) ;
290+ } catch ( error ) {
291+ console . error ( "Error creating GitHub issue:" , error ) ;
292+ return c . json (
293+ {
294+ error : "Failed to submit issue" ,
295+ code : "NETWORK_ERROR" ,
296+ details : "Please check your connection and try again" ,
297+ } as ErrorResponse ,
298+ 500 ,
299+ ) ;
300+ }
201301 } ,
202302 )
203303 . get (
@@ -210,34 +310,130 @@ const app = new Hono()
210310 ) ,
211311 async ( c ) => {
212312 const { tag } = c . req . valid ( "query" ) ;
313+
314+ // Validate tag parameter
315+ if ( ! tag || tag . trim ( ) . length === 0 ) {
316+ return c . json (
317+ {
318+ error : "Tag parameter is required" ,
319+ code : "MISSING_TAG" ,
320+ } as ErrorResponse ,
321+ 400 ,
322+ ) ;
323+ }
324+
213325 const {
214326 GITHUB_CLIENT_ID ,
215327 GITHUB_APP_PRIVATE_KEY ,
216328 GITHUB_INSTALLATION_ID ,
217329 } = env < GithubEnv > ( c ) ;
218330 const privateKey = atob ( GITHUB_APP_PRIVATE_KEY ) ;
219331
220- const accessToken = await getInstallationAccessToken (
221- GITHUB_CLIENT_ID ,
222- privateKey ,
223- GITHUB_INSTALLATION_ID ,
224- ) ;
332+ let accessToken : string ;
333+ try {
334+ accessToken = await getInstallationAccessToken (
335+ GITHUB_CLIENT_ID ,
336+ privateKey ,
337+ GITHUB_INSTALLATION_ID ,
338+ ) ;
339+ } catch ( error ) {
340+ console . error ( "Failed to get GitHub access token for GET:" , error ) ;
341+ return c . json (
342+ {
343+ error : "Failed to authenticate with GitHub" ,
344+ code : "GITHUB_AUTH_FAILED" ,
345+ details : "Please try again later" ,
346+ } as ErrorResponse ,
347+ 500 ,
348+ ) ;
349+ }
350+
225351 const repoOwner = "nthumodifications" ;
226352 const repoName = "courseweb" ;
227353
228- const response = await fetch (
229- `https://api.github.com/repos/${ repoOwner } /${ repoName } /issues?filter=all&labels=${ tag } &state=open` ,
230- {
231- method : "GET" ,
232- headers : {
233- "User-Agent" : "nthumods-app" ,
234- Authorization : `token ${ accessToken } ` ,
235- Accept : "application/vnd.github.v3+json" ,
354+ try {
355+ const response = await fetch (
356+ `https://api.github.com/repos/${ repoOwner } /${ repoName } /issues?filter=all&labels=${ encodeURIComponent ( tag ) } &state=open` ,
357+ {
358+ method : "GET" ,
359+ headers : {
360+ "User-Agent" : "nthumods-app" ,
361+ Authorization : `token ${ accessToken } ` ,
362+ Accept : "application/vnd.github.v3+json" ,
363+ } ,
236364 } ,
237- } ,
238- ) ;
239- const data = ( await response . json ( ) ) as GithubIssue [ ] ;
240- return c . json ( data ) ;
365+ ) ;
366+
367+ if ( ! response . ok ) {
368+ const errorText = await response . text ( ) ;
369+ console . error (
370+ `GitHub API GET error: ${ response . status } ${ errorText } ` ,
371+ ) ;
372+
373+ let errorResponse : ErrorResponse ;
374+
375+ switch ( response . status ) {
376+ case 401 :
377+ errorResponse = {
378+ error : "Authentication failed with GitHub" ,
379+ code : "GITHUB_AUTH_INVALID" ,
380+ details : "Please refresh and try again" ,
381+ } ;
382+ break ;
383+ case 403 :
384+ errorResponse = {
385+ error : "Access denied by GitHub" ,
386+ code : "GITHUB_ACCESS_DENIED" ,
387+ details : "Rate limit exceeded or insufficient permissions" ,
388+ } ;
389+ break ;
390+ case 404 :
391+ errorResponse = {
392+ error : "Repository not found" ,
393+ code : "GITHUB_REPO_NOT_FOUND" ,
394+ details : "The repository may have been moved or deleted" ,
395+ } ;
396+ break ;
397+ default :
398+ errorResponse = {
399+ error : "Failed to fetch issues from GitHub" ,
400+ code : "GITHUB_API_ERROR" ,
401+ details : `HTTP ${ response . status } : ${ response . statusText } ` ,
402+ } ;
403+ }
404+
405+ return c . json (
406+ errorResponse ,
407+ ( response . status >= 500 ? 500 : response . status ) as any ,
408+ ) ;
409+ }
410+
411+ const data = ( await response . json ( ) ) as GithubIssue [ ] ;
412+
413+ // Validate response data
414+ if ( ! Array . isArray ( data ) ) {
415+ return c . json (
416+ {
417+ error : "Invalid response from GitHub" ,
418+ code : "INVALID_GITHUB_RESPONSE" ,
419+ details : "Expected an array of issues" ,
420+ } as ErrorResponse ,
421+ 502 ,
422+ ) ;
423+ }
424+
425+ return c . json ( data ) ;
426+ } catch ( error ) {
427+ console . error ( "Error fetching GitHub issues:" , error ) ;
428+ return c . json (
429+ {
430+ error : "Failed to fetch issues" ,
431+ code : "NETWORK_ERROR" ,
432+ details : "Please check your connection and try again" ,
433+ } as ErrorResponse ,
434+ 500 ,
435+ ) ;
436+ }
241437 } ,
242438 ) ;
243439
0 commit comments