Fatma12Atef commited on
Commit
dc1a0ff
Β·
verified Β·
1 Parent(s): ad8efb4

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +236 -240
main.py CHANGED
@@ -1,242 +1,238 @@
1
- from fastapi import FastAPI, HTTPException
2
- from fastapi.middleware.cors import CORSMiddleware
3
- from pydantic import BaseModel
4
- import uuid
5
- import json
6
- import os
7
- import uvicorn
8
- import shutil
9
- from pymongo import MongoClient
10
- from dotenv import load_dotenv
11
- from pathlib import Path
12
-
13
- # Import your scripts
14
- import generator
15
- import judge
16
-
17
- # ==========================================
18
- # 1. SETUP ENV & DATABASE
19
- # ==========================================
20
-
21
- # Locate and load .env from backend folder
22
- current_file = Path(__file__).resolve()
23
- from dotenv import load_dotenv
24
- load_dotenv() # HF will inject secrets automatically
25
-
26
- if env_path.exists():
27
- load_dotenv(dotenv_path=env_path)
28
- print("βœ… [PS Service] .env loaded successfully.")
29
- else:
30
- print(f"⚠️ [PS Service] Warning: .env not found at {env_path}")
31
- MONGO_URI = os.getenv("MONGO_URI")
32
-
33
- # Initialize MongoDB
34
- problems_collection = None
35
- if MONGO_URI:
36
- try:
37
  client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000)
38
-
39
- db = client["acemock"]
40
- problems_collection = db["coding_problems"]
41
- print("βœ… [PS Service] Connected to MongoDB.")
42
- except Exception as e:
43
- print(f"❌ [PS Service] MongoDB Connection Error: {e}")
44
- else:
45
- print("❌ [PS Service] MONGO_URI not found. Database features will fail.")
46
-
47
- app = FastAPI(title="AceMock Problem Solving")
48
-
49
- # Enable CORS
50
- app.add_middleware(
51
- CORSMiddleware,
52
- allow_origins=["http://localhost:3001", "http://127.0.0.1:3001"],
53
- allow_credentials=True,
54
- allow_methods=["*"],
55
- allow_headers=["*"],
56
- )
57
-
58
- # Ensure temp directory exists for running code
59
- os.makedirs("temp", exist_ok=True)
60
-
61
- # --- HELPER: Normalize Language ---
62
- def normalize_lang(lang: str):
63
- lang = lang.lower()
64
- if lang == "javascript": return "js"
65
- if lang == "c++": return "cpp"
66
- return lang
67
-
68
- # --- DATA MODELS ---
69
- class ProblemRequest(BaseModel):
70
- level: str # "Fresh", "Junior", "Senior"
71
- language: str # "cpp", "python", "javascript"
72
-
73
- class ExecuteRequest(BaseModel):
74
- language: str
75
- code: str
76
- input: str = "" # User's custom input
77
-
78
- class SubmissionRequest(BaseModel):
79
- problem_id: str
80
- user_code: str
81
- language: str
82
-
83
- # --- ENDPOINT 1: GENERATE PROBLEM (MongoDB) ---
84
- @app.post("/generate-problem")
85
- def get_problem(req: ProblemRequest):
86
- # βœ… FIX: Explicit None check for PyMongo 4+ compatibility
87
- if problems_collection is None:
88
- raise HTTPException(status_code=500, detail="Database connection not available.")
89
-
90
- print(f"Generating {req.level} problem in {req.language}...")
91
-
92
- problem_data = None
93
- # Retry logic (AI can fail occasionally)
94
- for _ in range(3):
95
- try:
96
- problem_data = generator.generate_problem_for_api(req.level, normalize_lang(req.language))
97
- if problem_data: break
98
- except Exception as e:
99
- print(f"Generation attempt failed: {e}")
100
-
101
- if not problem_data:
102
- raise HTTPException(status_code=500, detail="AI generation failed. Please try again.")
103
-
104
- # Generate a unique ID for this session
105
- problem_id = str(uuid.uuid4())
106
-
107
- # Add ID to data
108
- problem_data["_id"] = problem_id
109
- problem_data["level"] = req.level
110
- problem_data["language"] = req.language
111
- problem_data["created_at"] = str(uuid.uuid1()) # Timestamp roughly
112
-
113
- # # βœ… SAVE TO LOCAL JSON FILE
114
- # try:
115
- # # Ensure the 'problems' directory exists
116
- # problems_dir = os.path.join(os.path.dirname(__file__), "problems")
117
- # os.makedirs(problems_dir, exist_ok=True)
118
-
119
- # # Save the file using the problem_id as the name
120
- # file_path = os.path.join(problems_dir, f"{problem_id}.json")
121
- # with open(file_path, "w", encoding="utf-8") as f:
122
- # json.dump(problem_data, f, indent=4, ensure_ascii=False)
123
- # print(f"βœ… Problem saved to local JSON: {file_path}")
124
- # except Exception as e:
125
- # print(f"⚠️ Failed to save problem to JSON file: {e}")
126
-
127
- # βœ… SAVE TO MONGODB
128
- try:
129
- problems_collection.insert_one(problem_data)
130
- print(f"βœ… Problem saved to MongoDB with ID: {problem_id}")
131
- except Exception as e:
132
- print(f"❌ Failed to save to DB: {e}")
133
- raise HTTPException(status_code=500, detail="Database save failed.")
134
-
135
- # Return ONLY what the user needs to see (Hide test cases!)
136
- return {
137
- "problem_id": problem_id,
138
- "title": problem_data['title'],
139
- "description": problem_data['description'],
140
- "input_format": problem_data['input_format'],
141
- "output_format": problem_data['output_format'],
142
- "samples": problem_data['samples'],
143
- "solution_code": problem_data.get('solution_code')
144
- }
145
-
146
- # --- ENDPOINT 2: EXECUTE CODE (Run Button - No DB needed) ---
147
- @app.post("/execute")
148
- def execute_code(req: ExecuteRequest):
149
- lang_key = normalize_lang(req.language)
150
-
151
- # Create temp file
152
- ext_map = {"cpp": ".cpp", "python": ".py", "js": ".js"}
153
- filename = f"run_{uuid.uuid4()}{ext_map.get(lang_key, '.txt')}"
154
- filepath = f"temp/{filename}"
155
-
156
- with open(filepath, "w") as f:
157
- f.write(req.code)
158
-
159
- try:
160
- # Run using the exact same Judge logic as submit
161
- result = judge.run_test_case(filepath, lang_key, req.input)
162
-
163
- # Cleanup
164
- if os.path.exists(filepath): os.remove(filepath)
165
-
166
- return result
167
-
168
- except Exception as e:
169
- if os.path.exists(filepath): os.remove(filepath)
170
- return {"status": "Error", "output": str(e)}
171
-
172
- # --- ENDPOINT 3: SUBMIT SOLUTION (Grading via MongoDB) ---
173
- @app.post("/submit")
174
- def submit_solution(req: SubmissionRequest):
175
- if problems_collection is None:
176
- raise HTTPException(status_code=500, detail="Database connection not available.")
177
-
178
- # 1. Fetch Problem from MongoDB
179
- problem_data = problems_collection.find_one({"_id": req.problem_id})
180
-
181
- if not problem_data:
182
- raise HTTPException(status_code=404, detail="Problem ID not found in database.")
183
-
184
- lang_key = normalize_lang(req.language)
185
- ext_map = {"cpp": ".cpp", "python": ".py", "js": ".js"}
186
- filename = f"sub_{req.problem_id}{ext_map.get(lang_key, '.txt')}"
187
- user_file_path = f"temp/{filename}"
188
-
189
- with open(user_file_path, "w") as f:
190
- f.write(req.user_code)
191
-
192
- # 3. Run Test Cases (Retrieved from DB)
193
- results = []
194
- passed_count = 0
195
- total_tests = len(problem_data['test_cases'])
196
-
197
- for test in problem_data['test_cases']:
198
- run_res = judge.run_test_case(
199
- filepath=user_file_path,
200
- lang=lang_key,
201
- input_str=test['input']
202
- )
203
-
204
- if run_res['status'] == "Success":
205
- if run_res['output'].strip() == test['expected_output'].strip():
206
- results.append({"id": test['id'], "status": "Passed"})
207
- passed_count += 1
208
- else:
209
- results.append({
210
- "id": test['id'],
211
- "status": "Wrong Answer",
212
- "your_output": run_res['output'][:50],
213
- "expected": test['expected_output'].strip()[:100]
214
- })
215
- else:
216
- results.append({"id": test['id'], "status": run_res['status'], "details": run_res['output']})
217
-
218
- # 4. Cleanup (Delete temp file)
219
- if os.path.exists(user_file_path):
220
- os.remove(user_file_path)
221
-
222
- db_solutions = problem_data.get("solution_code", {})
223
-
224
- if isinstance(db_solutions, dict):
225
- # Fetch the requested language. Fallback to python if it's missing.
226
- final_solution = db_solutions.get(lang_key, db_solutions.get("python", "# Solution missing"))
227
- else:
228
- # Fallback logic just in case an old problem (string format) is tested
229
- final_solution = str(db_solutions)
230
-
231
- # 5. Return Score AND The Match Solution
232
- return {
233
- "passed": passed_count,
234
- "total": total_tests,
235
- "score": round((passed_count / total_tests) * 100, 1),
236
- "details": results,
237
- "solution_code": final_solution
238
- }
239
-
240
- # --- RUN ON PORT 8003 ---
241
- if __name__ == "__main__":
242
  uvicorn.run(app, host="0.0.0.0", port=8003)
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from pydantic import BaseModel
4
+ import uuid
5
+ import json
6
+ import os
7
+ import uvicorn
8
+ import shutil
9
+ from pymongo import MongoClient
10
+ from dotenv import load_dotenv
11
+ from pathlib import Path
12
+
13
+ # Import your scripts
14
+ import generator
15
+ import judge
16
+
17
+ # ==========================================
18
+ # 1. SETUP ENV & DATABASE
19
+ # ==========================================
20
+
21
+ # Locate and load .env from backend folder
22
+
23
+ from dotenv import load_dotenv
24
+ load_dotenv() # HF will inject secrets automatically
25
+
26
+
27
+ MONGO_URI = os.getenv("MONGO_URI")
28
+
29
+ # Initialize MongoDB
30
+ problems_collection = None
31
+ if MONGO_URI:
32
+ try:
 
 
 
 
33
  client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000)
34
+
35
+ db = client["acemock"]
36
+ problems_collection = db["coding_problems"]
37
+ print("βœ… [PS Service] Connected to MongoDB.")
38
+ except Exception as e:
39
+ print(f"❌ [PS Service] MongoDB Connection Error: {e}")
40
+ else:
41
+ print("❌ [PS Service] MONGO_URI not found. Database features will fail.")
42
+
43
+ app = FastAPI(title="AceMock Problem Solving")
44
+
45
+ # Enable CORS
46
+ app.add_middleware(
47
+ CORSMiddleware,
48
+ allow_origins=["http://localhost:3001", "http://127.0.0.1:3001"],
49
+ allow_credentials=True,
50
+ allow_methods=["*"],
51
+ allow_headers=["*"],
52
+ )
53
+
54
+ # Ensure temp directory exists for running code
55
+ os.makedirs("temp", exist_ok=True)
56
+
57
+ # --- HELPER: Normalize Language ---
58
+ def normalize_lang(lang: str):
59
+ lang = lang.lower()
60
+ if lang == "javascript": return "js"
61
+ if lang == "c++": return "cpp"
62
+ return lang
63
+
64
+ # --- DATA MODELS ---
65
+ class ProblemRequest(BaseModel):
66
+ level: str # "Fresh", "Junior", "Senior"
67
+ language: str # "cpp", "python", "javascript"
68
+
69
+ class ExecuteRequest(BaseModel):
70
+ language: str
71
+ code: str
72
+ input: str = "" # User's custom input
73
+
74
+ class SubmissionRequest(BaseModel):
75
+ problem_id: str
76
+ user_code: str
77
+ language: str
78
+
79
+ # --- ENDPOINT 1: GENERATE PROBLEM (MongoDB) ---
80
+ @app.post("/generate-problem")
81
+ def get_problem(req: ProblemRequest):
82
+ # βœ… FIX: Explicit None check for PyMongo 4+ compatibility
83
+ if problems_collection is None:
84
+ raise HTTPException(status_code=500, detail="Database connection not available.")
85
+
86
+ print(f"Generating {req.level} problem in {req.language}...")
87
+
88
+ problem_data = None
89
+ # Retry logic (AI can fail occasionally)
90
+ for _ in range(3):
91
+ try:
92
+ problem_data = generator.generate_problem_for_api(req.level, normalize_lang(req.language))
93
+ if problem_data: break
94
+ except Exception as e:
95
+ print(f"Generation attempt failed: {e}")
96
+
97
+ if not problem_data:
98
+ raise HTTPException(status_code=500, detail="AI generation failed. Please try again.")
99
+
100
+ # Generate a unique ID for this session
101
+ problem_id = str(uuid.uuid4())
102
+
103
+ # Add ID to data
104
+ problem_data["_id"] = problem_id
105
+ problem_data["level"] = req.level
106
+ problem_data["language"] = req.language
107
+ problem_data["created_at"] = str(uuid.uuid1()) # Timestamp roughly
108
+
109
+ # # βœ… SAVE TO LOCAL JSON FILE
110
+ # try:
111
+ # # Ensure the 'problems' directory exists
112
+ # problems_dir = os.path.join(os.path.dirname(__file__), "problems")
113
+ # os.makedirs(problems_dir, exist_ok=True)
114
+
115
+ # # Save the file using the problem_id as the name
116
+ # file_path = os.path.join(problems_dir, f"{problem_id}.json")
117
+ # with open(file_path, "w", encoding="utf-8") as f:
118
+ # json.dump(problem_data, f, indent=4, ensure_ascii=False)
119
+ # print(f"βœ… Problem saved to local JSON: {file_path}")
120
+ # except Exception as e:
121
+ # print(f"⚠️ Failed to save problem to JSON file: {e}")
122
+
123
+ # βœ… SAVE TO MONGODB
124
+ try:
125
+ problems_collection.insert_one(problem_data)
126
+ print(f"βœ… Problem saved to MongoDB with ID: {problem_id}")
127
+ except Exception as e:
128
+ print(f"❌ Failed to save to DB: {e}")
129
+ raise HTTPException(status_code=500, detail="Database save failed.")
130
+
131
+ # Return ONLY what the user needs to see (Hide test cases!)
132
+ return {
133
+ "problem_id": problem_id,
134
+ "title": problem_data['title'],
135
+ "description": problem_data['description'],
136
+ "input_format": problem_data['input_format'],
137
+ "output_format": problem_data['output_format'],
138
+ "samples": problem_data['samples'],
139
+ "solution_code": problem_data.get('solution_code')
140
+ }
141
+
142
+ # --- ENDPOINT 2: EXECUTE CODE (Run Button - No DB needed) ---
143
+ @app.post("/execute")
144
+ def execute_code(req: ExecuteRequest):
145
+ lang_key = normalize_lang(req.language)
146
+
147
+ # Create temp file
148
+ ext_map = {"cpp": ".cpp", "python": ".py", "js": ".js"}
149
+ filename = f"run_{uuid.uuid4()}{ext_map.get(lang_key, '.txt')}"
150
+ filepath = f"temp/{filename}"
151
+
152
+ with open(filepath, "w") as f:
153
+ f.write(req.code)
154
+
155
+ try:
156
+ # Run using the exact same Judge logic as submit
157
+ result = judge.run_test_case(filepath, lang_key, req.input)
158
+
159
+ # Cleanup
160
+ if os.path.exists(filepath): os.remove(filepath)
161
+
162
+ return result
163
+
164
+ except Exception as e:
165
+ if os.path.exists(filepath): os.remove(filepath)
166
+ return {"status": "Error", "output": str(e)}
167
+
168
+ # --- ENDPOINT 3: SUBMIT SOLUTION (Grading via MongoDB) ---
169
+ @app.post("/submit")
170
+ def submit_solution(req: SubmissionRequest):
171
+ if problems_collection is None:
172
+ raise HTTPException(status_code=500, detail="Database connection not available.")
173
+
174
+ # 1. Fetch Problem from MongoDB
175
+ problem_data = problems_collection.find_one({"_id": req.problem_id})
176
+
177
+ if not problem_data:
178
+ raise HTTPException(status_code=404, detail="Problem ID not found in database.")
179
+
180
+ lang_key = normalize_lang(req.language)
181
+ ext_map = {"cpp": ".cpp", "python": ".py", "js": ".js"}
182
+ filename = f"sub_{req.problem_id}{ext_map.get(lang_key, '.txt')}"
183
+ user_file_path = f"temp/{filename}"
184
+
185
+ with open(user_file_path, "w") as f:
186
+ f.write(req.user_code)
187
+
188
+ # 3. Run Test Cases (Retrieved from DB)
189
+ results = []
190
+ passed_count = 0
191
+ total_tests = len(problem_data['test_cases'])
192
+
193
+ for test in problem_data['test_cases']:
194
+ run_res = judge.run_test_case(
195
+ filepath=user_file_path,
196
+ lang=lang_key,
197
+ input_str=test['input']
198
+ )
199
+
200
+ if run_res['status'] == "Success":
201
+ if run_res['output'].strip() == test['expected_output'].strip():
202
+ results.append({"id": test['id'], "status": "Passed"})
203
+ passed_count += 1
204
+ else:
205
+ results.append({
206
+ "id": test['id'],
207
+ "status": "Wrong Answer",
208
+ "your_output": run_res['output'][:50],
209
+ "expected": test['expected_output'].strip()[:100]
210
+ })
211
+ else:
212
+ results.append({"id": test['id'], "status": run_res['status'], "details": run_res['output']})
213
+
214
+ # 4. Cleanup (Delete temp file)
215
+ if os.path.exists(user_file_path):
216
+ os.remove(user_file_path)
217
+
218
+ db_solutions = problem_data.get("solution_code", {})
219
+
220
+ if isinstance(db_solutions, dict):
221
+ # Fetch the requested language. Fallback to python if it's missing.
222
+ final_solution = db_solutions.get(lang_key, db_solutions.get("python", "# Solution missing"))
223
+ else:
224
+ # Fallback logic just in case an old problem (string format) is tested
225
+ final_solution = str(db_solutions)
226
+
227
+ # 5. Return Score AND The Match Solution
228
+ return {
229
+ "passed": passed_count,
230
+ "total": total_tests,
231
+ "score": round((passed_count / total_tests) * 100, 1),
232
+ "details": results,
233
+ "solution_code": final_solution
234
+ }
235
+
236
+ # --- RUN ON PORT 8003 ---
237
+ if __name__ == "__main__":
238
  uvicorn.run(app, host="0.0.0.0", port=8003)