Skip to content

Commit 506e36e

Browse files
committed
Merge branch 'development' into deployment
2 parents d9fb768 + 920dd80 commit 506e36e

File tree

106 files changed

+4199
-722
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+4199
-722
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,17 @@ docker-compose down
2727
## Useful links
2828

2929
- User Service: http://localhost:3001
30+
3031
- Question Service: http://localhost:3000
32+
3133
- Matching Service: http://localhost:3002
34+
3235
- Collab Service: http://localhost:3003
36+
3337
- Code Execution Service: http://localhost:3004
38+
39+
- Communication Service: http://localhost:3005
40+
3441
- Question History Service: http://localhost:3006
42+
3543
- Frontend: http://localhost:5173

backend/README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,23 @@
22

33
> Before proceeding to each microservice for more instructions:
44
5-
1. Set up cloud MongoDB if not using docker. We recommend this if you are just testing out each microservice separately to avoid needing to manually set up multiple instances of local MongoDB. Else, if you are using docker-compose.yml to run PeerPrep, check out the READMEs in the different backend microservices to set up the env for the local MongoDB instances.
5+
1. Set up cloud MongoDB if you are not using Docker. We recommend this if you are just testing out each microservice separately to avoid needing to manually set up multiple instances of local MongoDB. Otherwise, if you are using `docker-compose.yml` to run PeerPrep, check out the READMEs in the different backend microservices to set up the `.env` files for the local MongoDB instances.
66

77
2. Set up Firebase.
88

9-
3. Follow the instructions [here](https://nodejs.org/en/download/package-manager) to set up Node v20.
9+
3. For the microservices that use Redis, to view the contents stored:
10+
11+
1. Go to [http://localhost:5540](http://localhost:5540).
12+
13+
2. Click on "Add Redis Database".
14+
15+
3. Enter `host.docker.internal` as the Host.
16+
17+
4. Enter the port used by the respective service:
18+
- User Service: `6379`
19+
- Collab Service: `6380`
20+
21+
4. Follow the instructions [here](https://nodejs.org/en/download/package-manager) to set up Node v20.
1022

1123
## Setting-up cloud MongoDB (in production)
1224

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
NODE_ENV=development
22
SERVICE_PORT=3004
33

4+
# Origins for cors
45
ORIGINS=http://localhost:5173,http://127.0.0.1:5173
56

6-
# One Compiler
7+
# Other service APIs
8+
QUESTION_SERVICE_URL=http://question-service:3000/api/questions
9+
10+
# One Compiler configuration
711
ONE_COMPILER_URL=https://onecompiler-apis.p.rapidapi.com/api/v1/run
8-
ONE_COMPILER_KEY=<ONE_COMPILER_KEY>
12+
ONE_COMPILER_KEY=<ONE_COMPILER_KEY>

backend/code-execution-service/README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,22 @@
66

77
2. Sign up for a free OneCompiler API [here](https://rapidapi.com/onecompiler-onecompiler-default/api/onecompiler-apis).
88

9-
3. Update `ONE_COMPILER_KEY` in `.env` with the the value of `x-rapidapi-key`.
9+
3. Update `ONE_COMPILER_KEY` in the `.env` file with the value of `x-rapidapi-key`.
1010

11-
## Running Code Execution Service without Docker
11+
## Running Code Execution Service Locally
1212

1313
1. Open Command Line/Terminal and navigate into the `code-execution-service` directory.
1414

1515
2. Run the command: `npm install`. This will install all the necessary dependencies.
1616

1717
3. Run the command `npm start` to start the Code Execution Service in production mode, or use `npm run dev` for development mode, which includes features like automatic server restart when you make code changes.
1818

19+
## Running Code Execution Service with Docker
20+
21+
1. Open the Command Line/Terminal.
22+
23+
2. Run the command `docker compose run code-execution-service` to start up the Code Execution Service and its dependencies.
24+
1925
## After running
2026

2127
1. To view Code Execution Service documentation, go to http://localhost:3004/docs.

backend/code-execution-service/src/controllers/codeExecutionControllers.ts

Lines changed: 68 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import {
77
ERROR_FAILED_TO_EXECUTE_MESSAGE,
88
ERROR_NOT_SAME_LENGTH_MESSAGE,
99
SUCCESS_MESSAGE,
10+
ERROR_INVALID_TEST_CASES_MESSAGE,
1011
} from "../utils/constants";
12+
import { questionService } from "../utils/questionApi";
13+
import { testCasesApi } from "../utils/testCasesApi";
1114

1215
interface CompilerResult {
1316
status: string;
@@ -19,14 +22,9 @@ interface CompilerResult {
1922
}
2023

2124
export const executeCode = async (req: Request, res: Response) => {
22-
const {
23-
language,
24-
code,
25-
stdinList,
26-
stdoutList: expectedStdoutList,
27-
} = req.body;
25+
const { questionId, language, code } = req.body;
2826

29-
if (!language || !code || !stdinList || !expectedStdoutList) {
27+
if (!language || !code || !questionId) {
3028
res.status(400).json({
3129
message: ERROR_MISSING_REQUIRED_FIELDS_MESSAGE,
3230
});
@@ -40,48 +38,76 @@ export const executeCode = async (req: Request, res: Response) => {
4038
return;
4139
}
4240

43-
if (stdinList.length !== expectedStdoutList.length) {
44-
res.status(400).json({
45-
message: ERROR_NOT_SAME_LENGTH_MESSAGE,
46-
});
47-
return;
48-
}
49-
5041
try {
51-
const response = await oneCompilerApi(language, stdinList, code);
42+
// Get question test case files
43+
const qnsResponse = await questionService.get(`/${questionId}`);
44+
const { inputs: stdinList, outputs: expectedResultList } =
45+
qnsResponse.data.question;
5246

53-
const data = (response.data as CompilerResult[]).map((result, index) => {
54-
const {
55-
status,
56-
exception,
57-
stdout: actualStdout,
58-
stderr,
59-
stdin,
60-
executionTime,
61-
} = result;
62-
const expectedStdout = expectedStdoutList[index];
47+
// Extract test cases from input and output files
48+
// const testCases = await testCasesApi(
49+
// testcaseInputFileUrl,
50+
// testcaseOutputFileUrl
51+
// );
6352

64-
return {
65-
status,
66-
exception,
67-
expectedStdout,
68-
actualStdout,
69-
stderr,
70-
stdin,
71-
executionTime,
72-
isMatch:
73-
stderr !== null
74-
? false
75-
: actualStdout.trim() === expectedStdout.trim(),
76-
};
77-
});
53+
// const stdinList: string[] = testCases.input;
54+
// const expectedResultList: string[] = testCases.output;
55+
56+
if (stdinList.length !== expectedResultList.length) {
57+
res.status(400).json({
58+
message: ERROR_NOT_SAME_LENGTH_MESSAGE,
59+
});
60+
return;
61+
}
62+
63+
if (stdinList.length === 0) {
64+
res.status(400).json({
65+
message: ERROR_INVALID_TEST_CASES_MESSAGE,
66+
});
67+
return;
68+
}
69+
70+
// Execute code for each test case
71+
const compilerResponse = await oneCompilerApi(language, stdinList, code);
72+
73+
const compilerData = (compilerResponse.data as CompilerResult[]).map(
74+
(result, index) => {
75+
let { stdout, ...restofResult } = result; // eslint-disable-line
76+
const expectedResultValue = expectedResultList[index].trim();
77+
78+
if (!stdout) {
79+
stdout = "";
80+
}
81+
82+
let resultValue = "";
83+
if (restofResult.stderr) {
84+
resultValue = "";
85+
stdout = stdout.trim();
86+
} else {
87+
// Extract the last line as the result value and the rest as stdout only if there is no error
88+
const lines = stdout.trim().split("\n");
89+
resultValue = lines.pop() || "";
90+
stdout = lines.join("\n");
91+
}
92+
93+
return {
94+
...restofResult,
95+
stdout,
96+
actualResult: resultValue,
97+
expectedResult: expectedResultValue,
98+
isMatch:
99+
result.stderr !== null
100+
? false
101+
: resultValue === expectedResultValue,
102+
};
103+
}
104+
);
78105

79106
res.status(200).json({
80107
message: SUCCESS_MESSAGE,
81-
data,
108+
data: compilerData,
82109
});
83-
} catch (err) {
84-
console.log(err);
110+
} catch {
85111
res.status(500).json({ message: ERROR_FAILED_TO_EXECUTE_MESSAGE });
86112
}
87113
};

backend/code-execution-service/src/utils/constants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export const SUPPORTED_LANGUAGES = ["python", "java", "c"];
22

33
export const ERROR_MISSING_REQUIRED_FIELDS_MESSAGE =
4-
"Missing required fields: language, code, stdinList, or stdoutList";
4+
"Missing required fields: language, code, or questionId.";
55

66
export const ERROR_UNSUPPORTED_LANGUAGE_MESSAGE = "Unsupported language.";
77

@@ -11,3 +11,5 @@ export const ERROR_NOT_SAME_LENGTH_MESSAGE =
1111
export const ERROR_FAILED_TO_EXECUTE_MESSAGE = "Failed to execute code";
1212

1313
export const SUCCESS_MESSAGE = "Code executed successfully";
14+
15+
export const ERROR_INVALID_TEST_CASES_MESSAGE = "Invalid test cases";

backend/code-execution-service/src/utils/oneCompilerApi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ interface FileType {
1010

1111
export const oneCompilerApi = async (
1212
language: string,
13-
stdin: string,
13+
stdin: string[],
1414
userCode: string
1515
) => {
1616
let files: FileType[] = [];
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import axios from "axios";
2+
3+
const QUESTION_SERVICE_URL =
4+
process.env.QUESTION_SERVICE_URL ||
5+
"http://question-service:3000/api/questions";
6+
7+
export const questionService = axios.create({
8+
baseURL: QUESTION_SERVICE_URL,
9+
headers: {
10+
"Content-Type": "application/json",
11+
},
12+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import axios from "axios";
2+
3+
export const testCasesApi = async (
4+
inputFileUrl: string,
5+
outputFileUrl: string
6+
) => {
7+
try {
8+
const inputFileUrlResponse = await axios.get(inputFileUrl);
9+
const outputFileUrlResponse = await axios.get(outputFileUrl);
10+
11+
// Split the input and output files by double new line
12+
return {
13+
input: inputFileUrlResponse.data.replace(/\r\n/g, "\n").split("\n\n"),
14+
output: outputFileUrlResponse.data.replace(/\r\n/g, "\n").split("\n\n"),
15+
};
16+
} catch {
17+
console.log("Failed to fetch test cases");
18+
return { input: [], output: [] };
19+
}
20+
};

backend/code-execution-service/swagger.yml

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ definitions:
1010
stdin:
1111
type: string
1212
required: true
13-
expectedStdout:
13+
stdout:
1414
type: string
1515
required: true
16-
actualStdout:
16+
expectedResult:
17+
type: string
18+
required: true
19+
actualResult:
1720
type: string
1821
isMatch:
1922
type: boolean
@@ -82,23 +85,11 @@ paths:
8285
code:
8386
type: string
8487
description: The source code to execute.
85-
example: "name = input()\nage = input()\nprint('Hello ' + name + '. You are ' + age + ' years old this year?')\n\n"
86-
stdinList:
87-
type: array
88-
description: List of standard input values to pass to the code.
89-
items:
90-
type: string
91-
example: ["Alice\n21", "Peter\n22"]
92-
stdoutList:
93-
type: array
94-
description: Expected standard output values to compare against.
95-
items:
96-
type: string
97-
example:
98-
[
99-
"Hello Alice. You are 21 years old this year?\n",
100-
"Hello Peter. You are 22 years old this year?\n",
101-
]
88+
example: "a=input()\nb=input()\nc=input()\nd=input()\n\nprint(int(a)+int(b)+int(c)+int(d))"
89+
questionId:
90+
type: string
91+
description: Question ID.
92+
example: "123456789"
10293
responses:
10394
200:
10495
description: Execution Result

0 commit comments

Comments
 (0)