1+ import { Hono } from "hono" ;
2+ import { zValidator } from "@hono/zod-validator" ;
3+ import { HTTPException } from "hono/http-exception" ;
4+ import { z } from "zod" ;
5+
6+ const codeRouter = new Hono ( ) ;
7+
8+ // Environment variables
9+ const JUDGE0_API_URL = process . env . JUDGE0_API_URL || "https://judge0-ce.p.rapidapi.com" ;
10+ const JUDGE0_API_KEY = process . env . JUDGE0_API_KEY ;
11+ const JUDGE0_API_HOST = process . env . JUDGE0_API_HOST || "judge0-ce.p.rapidapi.com" ;
12+
13+ if ( ! JUDGE0_API_KEY ) {
14+ console . error ( "❌ JUDGE0_API_KEY environment variable is required" ) ;
15+ process . exit ( 1 ) ;
16+ }
17+
18+ // Validation schemas with proper security limits
19+ const executeCodeSchema = z . object ( {
20+ language_id : z . number ( ) . int ( ) . min ( 1 ) . max ( 200 ) , // Reasonable language ID range
21+ source_code : z . string ( ) . min ( 1 ) . max ( 50000 ) , // 50KB max source code
22+ stdin : z . string ( ) . max ( 10000 ) . optional ( ) . default ( "" ) , // 10KB max stdin
23+ cpu_time_limit : z . number ( ) . min ( 1 ) . max ( 30 ) . optional ( ) . default ( 5 ) , // 1-30 seconds
24+ memory_limit : z . number ( ) . min ( 16384 ) . max ( 512000 ) . optional ( ) . default ( 128000 ) , // 16MB-512MB
25+ wall_time_limit : z . number ( ) . min ( 1 ) . max ( 60 ) . optional ( ) . default ( 10 ) , // 1-60 seconds
26+ } ) ;
27+
28+ const tokenSchema = z . object ( {
29+ token : z . string ( ) . min ( 1 ) ,
30+ } ) ;
31+
32+ // Helper function to make Judge0 API requests
33+ async function makeJudge0Request ( endpoint : string , options : RequestInit = { } ) {
34+ const url = `${ JUDGE0_API_URL } ${ endpoint } ` ;
35+
36+ // Add timeout protection
37+ const controller = new AbortController ( ) ;
38+ const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 30000 ) ; // 30s timeout
39+
40+ try {
41+ const response = await fetch ( url , {
42+ ...options ,
43+ signal : controller . signal ,
44+ headers : {
45+ "X-RapidAPI-Key" : JUDGE0_API_KEY ,
46+ "X-RapidAPI-Host" : JUDGE0_API_HOST ,
47+ "Content-Type" : "application/json" ,
48+ ...options . headers ,
49+ } ,
50+ } ) ;
51+
52+ clearTimeout ( timeoutId ) ;
53+
54+ if ( ! response . ok ) {
55+ // Log full error details server-side only
56+ const errorBody = await response . text ( ) . catch ( ( ) => '' ) ;
57+ console . error ( `Judge0 API Error: ${ response . status } ${ response . statusText } - ${ errorBody } ` ) ;
58+
59+ // Return sanitized error messages to frontend
60+ let clientMessage = "Code execution failed. Please try again." ;
61+ let statusCode = response . status ;
62+
63+ if ( response . status === 429 ) {
64+ clientMessage = "Code execution service is temporarily busy. Please try again in a few minutes." ;
65+ } else if ( response . status === 401 || response . status === 403 ) {
66+ clientMessage = "Code execution service is temporarily unavailable. Please contact support." ;
67+ statusCode = 503 ; // Don't expose auth issues
68+ } else if ( response . status >= 500 ) {
69+ clientMessage = "Code execution service is temporarily unavailable. Please try again later." ;
70+ statusCode = 503 ;
71+ }
72+
73+ throw new HTTPException ( statusCode , {
74+ message : clientMessage ,
75+ } ) ;
76+ }
77+
78+ return response ;
79+ } catch ( error ) {
80+ clearTimeout ( timeoutId ) ;
81+
82+ if ( error instanceof HTTPException ) {
83+ throw error ;
84+ }
85+
86+ if ( error . name === 'AbortError' ) {
87+ console . error ( "Judge0 API timeout" ) ;
88+ throw new HTTPException ( 504 , {
89+ message : "Code execution timed out. Please try again."
90+ } ) ;
91+ }
92+
93+ console . error ( "Judge0 API request failed:" , error ) ;
94+ throw new HTTPException ( 503 , {
95+ message : "Code execution service temporarily unavailable" ,
96+ } ) ;
97+ }
98+ }
99+
100+ // POST /api/code/execute - Submit code for execution
101+ codeRouter . post (
102+ "/execute" ,
103+ zValidator ( "json" , executeCodeSchema ) ,
104+ async ( c ) => {
105+ try {
106+ const body = c . req . valid ( "json" ) ;
107+
108+ // Base64 encode the source code and stdin for Judge0
109+ const submissionData = {
110+ ...body ,
111+ source_code : Buffer . from ( body . source_code ) . toString ( "base64" ) ,
112+ stdin : Buffer . from ( body . stdin || "" ) . toString ( "base64" ) ,
113+ } ;
114+
115+ const response = await makeJudge0Request ( "/submissions?base64_encoded=true" , {
116+ method : "POST" ,
117+ body : JSON . stringify ( submissionData ) ,
118+ } ) ;
119+
120+ const result = await response . json ( ) ;
121+ return c . json ( result ) ;
122+ } catch ( error ) {
123+ if ( error instanceof HTTPException ) {
124+ throw error ;
125+ }
126+
127+ console . error ( "Code execution error:" , error ) ;
128+ throw new HTTPException ( 500 , {
129+ message : "Failed to submit code for execution" ,
130+ } ) ;
131+ }
132+ }
133+ ) ;
134+
135+ // GET /api/code/status/:token - Get execution status and results
136+ codeRouter . get (
137+ "/status/:token" ,
138+ zValidator ( "param" , tokenSchema ) ,
139+ async ( c ) => {
140+ try {
141+ const { token } = c . req . valid ( "param" ) ;
142+
143+ const response = await makeJudge0Request (
144+ `/submissions/${ token } ?base64_encoded=true`
145+ ) ;
146+
147+ const result = await response . json ( ) ;
148+
149+ // Decode base64 encoded fields if they exist
150+ if ( result . stdout ) {
151+ result . stdout = Buffer . from ( result . stdout , "base64" ) . toString ( "utf-8" ) ;
152+ }
153+ if ( result . stderr ) {
154+ result . stderr = Buffer . from ( result . stderr , "base64" ) . toString ( "utf-8" ) ;
155+ }
156+ if ( result . compile_output ) {
157+ result . compile_output = Buffer . from ( result . compile_output , "base64" ) . toString ( "utf-8" ) ;
158+ }
159+ if ( result . message ) {
160+ result . message = Buffer . from ( result . message , "base64" ) . toString ( "utf-8" ) ;
161+ }
162+
163+ return c . json ( result ) ;
164+ } catch ( error ) {
165+ if ( error instanceof HTTPException ) {
166+ throw error ;
167+ }
168+
169+ console . error ( "Status check error:" , error ) ;
170+ throw new HTTPException ( 500 , {
171+ message : "Failed to check execution status" ,
172+ } ) ;
173+ }
174+ }
175+ ) ;
176+
177+ // GET /api/code/languages - Get supported languages (optional endpoint)
178+ codeRouter . get ( "/languages" , async ( c ) => {
179+ try {
180+ const response = await makeJudge0Request ( "/languages" ) ;
181+ const result = await response . json ( ) ;
182+ return c . json ( result ) ;
183+ } catch ( error ) {
184+ if ( error instanceof HTTPException ) {
185+ throw error ;
186+ }
187+
188+ console . error ( "Languages fetch error:" , error ) ;
189+ throw new HTTPException ( 500 , {
190+ message : "Failed to fetch supported languages" ,
191+ } ) ;
192+ }
193+ } ) ;
194+
195+ // GET /api/code/health - Health check for Judge0 service connectivity
196+ codeRouter . get ( "/health" , async ( c ) => {
197+ try {
198+ const response = await makeJudge0Request ( "/languages" ) ;
199+
200+ if ( response . ok ) {
201+ return c . json ( {
202+ status : "healthy" ,
203+ judge0 : "connected" ,
204+ timestamp : new Date ( ) . toISOString ( ) ,
205+ } ) ;
206+ } else {
207+ return c . json ( {
208+ status : "degraded" ,
209+ judge0 : "partial_connectivity" ,
210+ timestamp : new Date ( ) . toISOString ( ) ,
211+ } , 207 ) ; // Multi-status
212+ }
213+ } catch ( error ) {
214+ console . error ( "Judge0 health check failed:" , error ) ;
215+ return c . json ( {
216+ status : "unhealthy" ,
217+ judge0 : "disconnected" ,
218+ timestamp : new Date ( ) . toISOString ( ) ,
219+ } , 503 ) ;
220+ }
221+ } ) ;
222+
223+ export default codeRouter ;
0 commit comments