Skip to content

Commit 3a8f2bc

Browse files
authored
Merge pull request #6 from CODESPACE-CE-PROJECT/CE-008-Compiler
fix: ifinite loop execution
2 parents b7247cb + 5fb0035 commit 3a8f2bc

11 files changed

Lines changed: 241 additions & 54 deletions

src/controllers/compiler.controller.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Request, Response } from "express";
2-
import { ISubmissionRequest } from "../interfaces/submission.interface";
2+
import { ISubmissionLearnify, ISubmissionRequest } from "../interfaces/submission.interface";
33
import { ICompileRequest } from "../interfaces/compiler.interface";
44
import { rabbitMQService } from "../services/rabbitmq.service";
55
import { RequestWithUser } from "../interfaces/auth.interface";
@@ -36,4 +36,12 @@ export const compilerController = {
3636
message: "Add To Queue Successfully",
3737
});
3838
},
39+
compilerCodeLearnify: async (req: Request, res: Response) => {
40+
const data: ISubmissionLearnify = req.body
41+
// check valid user in learnify database
42+
await rabbitMQService.sendDataToQueue("learnify-submission", data)
43+
return res.status(200).json({
44+
message: "Add To Queue Successfully",
45+
})
46+
}
3947
};

src/interfaces/compiler.interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface ICreateFile {
88

99
export interface ICompileFile {
1010
result: string;
11+
exeFile: string;
1112
}
1213

1314
export interface ICompileRequest {

src/interfaces/submission.interface.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,13 @@ export interface IResultSubmission {
4141
}
4242
}
4343

44+
export interface ISubmissionLearnify {
45+
userId: string,
46+
sourceCode: string,
47+
fileName: string,
48+
testCases: {
49+
input: string,
50+
expectedOutput: string,
51+
}[],
52+
language: languageType
53+
}

src/middlewares/auth.middleware.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,18 @@ export const authorization = async (
3535
return res.status(401).json({ message: "UNAUTHORIZED" });
3636
}
3737
};
38+
39+
export const authorizationAPIKey = async (req: Request, res: Response, next: NextFunction) => {
40+
const token: string | undefined = req.header('x-api-key')
41+
if (!token) {
42+
return res.status(401).json({ message: "Invalid API Key" });
43+
}
44+
try {
45+
if (token !== environment.LEARNIFY_TOKEN_API) {
46+
return res.status(401).json({ message: "Invalid API Key" });
47+
}
48+
next();
49+
} catch (error) {
50+
return res.status(401).json({ message: "Invalid API Key" });
51+
}
52+
}

src/routes/compiler.route.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Router } from "express";
22
import { compilerController } from "../controllers/compiler.controller";
3-
import { authorization } from "../middlewares/auth.middleware";
3+
import { authorization, authorizationAPIKey } from "../middlewares/auth.middleware";
44
const router = Router();
55

66

@@ -10,6 +10,8 @@ router.post(
1010
compilerController.addSubmissionToRabbitMQ,
1111
);
1212

13-
router.post("/", authorization, compilerController.compilerCode);
13+
router.post("/compiler", authorization, compilerController.compilerCode);
14+
15+
router.post("/submission/learnify", authorizationAPIKey, compilerController.compilerCodeLearnify)
1416

1517
export { router as compilerRouter };

src/schema/environment.schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export const environmentSchema = z.object({
88
REDISHOST: z.string(),
99
BACKEND_URL: z.string(),
1010
JWT_SECRET: z.string(),
11+
LEARNIFY_TOKEN_API: z.string()
1112
});

src/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ app.use(cors());
2222

2323
// API Route
2424
app.use("/", serverRouter);
25-
app.use("/compiler", compilerRouter);
25+
app.use("/", compilerRouter);
2626

2727
const errorHandler = async (
2828
err: any,

src/services/compiler.service.ts

Lines changed: 77 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import fs from "fs";
1+
import fs, { readFileSync, writeFileSync } from "fs";
22
import path from "path";
3-
import { execSync } from "child_process";
3+
import { execSync, spawn } from "child_process";
44
import {
55
ExecutionResult,
66
ICompileFile,
@@ -48,49 +48,70 @@ export const compilerService = {
4848
return { result: "Error to create file", filePath: "", file: "" };
4949
}
5050
},
51-
moveFileToIsolate: async (
52-
filePath: string,
53-
boxId: number,
54-
): Promise<IMoveFile> => {
55-
try {
56-
const sanboxPath = execSync(`isolate --init --box-id=${boxId}`);
57-
execSync(`mv ${filePath} ${sanboxPath.toString().trim()}/box/`);
58-
return { result: "", sanboxPath: `${sanboxPath.toString().trim()}/box` };
59-
} catch (error) {
60-
return { result: "Error Move File To isolate", sanboxPath: "" };
61-
}
62-
},
6351
compileFile: async (
64-
sanboxPath: string,
52+
filePath: string,
6553
language: languageType,
6654
file: string,
6755
): Promise<ICompileFile> => {
6856
try {
6957
const regex = /\w+/;
7058
const filenameMatch = file.match(regex);
59+
let execFolder = ''
60+
let exeFile = ''
7161
if (!filenameMatch) {
7262
throw new Error("INVALID_FILEPATH");
7363
}
74-
const fullPath = path.join(sanboxPath, file);
64+
const folderPath = path.resolve(__dirname, `../../temp`);
7565
if (language === languageType.C) {
66+
execFolder = path.resolve(folderPath, 'exe-c')
67+
exeFile = `${execFolder}/${filenameMatch}`
68+
if (!fs.existsSync(execFolder)) {
69+
fs.mkdirSync(execFolder, { recursive: true });
70+
}
7671
execSync(
77-
`gcc -w -std=c++14 ${fullPath} -o ${sanboxPath}/${filenameMatch}`,
72+
`gcc -w -std=c++14 ${filePath} -o ${exeFile}`,
7873
);
7974
} else if (language === languageType.CPP) {
75+
execFolder = path.resolve(folderPath, 'exe-cpp')
76+
exeFile = `${execFolder}/${filenameMatch}`
77+
if (!fs.existsSync(execFolder)) {
78+
fs.mkdirSync(execFolder, { recursive: true });
79+
}
8080
execSync(
81-
`g++ -w -std=c++14 ${fullPath} -o ${sanboxPath}/${filenameMatch}`,
81+
`g++ -w -std=c++14 ${filePath} -o ${exeFile}`,
8282
);
8383
} else if (language === languageType.JAVA) {
84-
execSync(`javac -d ${sanboxPath} ${fullPath}`);
84+
execFolder = path.resolve(folderPath, 'exe-java')
85+
exeFile = `${execFolder}/${filenameMatch}.class`
86+
if (!fs.existsSync(execFolder)) {
87+
fs.mkdirSync(execFolder, { recursive: true });
88+
}
89+
execSync(`javac -d ${execFolder} ${filePath}`);
90+
} else if (language === languageType.PYTHON) {
91+
exeFile = `${filePath}`
8592
}
86-
return { result: "" };
93+
return { result: "", exeFile: exeFile };
8794
} catch (error: any) {
8895
const err = error.stdout.toString()
8996
? error.stdout.toString()
9097
: error.stderr.toString();
91-
return { result: err };
98+
return { result: err, exeFile: "" };
9299
}
93100
},
101+
102+
moveFileToIsolate: async (
103+
exefilePath: string,
104+
boxId: number,
105+
): Promise<IMoveFile> => {
106+
try {
107+
const sanboxPath = execSync(`isolate --init --box-id=${boxId}`);
108+
execSync(`cp ${exefilePath} ${sanboxPath.toString().trim()}/box/`);
109+
return { result: "", sanboxPath: `${sanboxPath.toString().trim()}/box` };
110+
} catch (error) {
111+
return { result: "Error Move File To isolate", sanboxPath: "" };
112+
}
113+
},
114+
94115
Run: async (
95116
boxId: number,
96117
language: languageType,
@@ -99,34 +120,45 @@ export const compilerService = {
99120
file: string,
100121
): Promise<ExecutionResult> => {
101122
return new Promise(async (resolve, _reject) => {
123+
const inputFile = `${sanboxPath.toString().trim()}/input.txt`
124+
const outputFile = `${sanboxPath.toString().trim()}/output.txt`
125+
const metaFile = `${sanboxPath.toString().trim()}/meta.txt`
126+
writeFileSync(inputFile, input)
127+
writeFileSync(outputFile, '')
128+
writeFileSync(metaFile, '')
102129
try {
103-
execSync(`echo ${input} > ${sanboxPath.toString().trim()}/input.txt`);
104130
const filenameMatch = file.match(/\w+/);
105-
let stdout: string = "";
131+
let command = '';
106132
if (language === languageType.C || language === languageType.CPP) {
107-
stdout = execSync(
108-
`isolate --box-id=${boxId} --silent --stderr-to-stdout --stdin=input.txt --run ${filenameMatch}`,
109-
{ encoding: "utf-8" },
110-
);
111-
}
112-
else if (language === languageType.JAVA) {
113-
stdout = execSync(
114-
`isolate --box-id=${boxId} --silent --stderr-to-stdout -p --stdin=input.txt --run /usr/lib/jvm/java-17-openjdk-amd64/bin/java ${filenameMatch}`,
115-
{ encoding: "utf-8" },
116-
);
133+
command = `isolate --box-id=${boxId} --time=5 --wall-time=5 --stderr-to-stdout --stdin=input.txt --stdout=output.txt --meta=${metaFile} --silent --run ${filenameMatch}`;
134+
} else if (language === languageType.JAVA) {
135+
command = `isolate --box-id=${boxId} --time=5 --wall-time=5 --stderr-to-stdout --stdin=input.txt --stdout=output.txt --meta=${metaFile} --silent --run /usr/lib/jvm/java-17-openjdk-amd64/bin/java ${filenameMatch}`;
136+
} else if (language === languageType.PYTHON) {
137+
command = `isolate --box-id=${boxId} --time=5 --wall-time=5 --stderr-to-stdout --stdin=input.txt --stdout=output.txt --meta=${metaFile} --silent --run /usr/bin/python3 ${file}`;
117138
}
118-
else if (language === languageType.PYTHON) {
119-
stdout = execSync(
120-
`isolate --box-id=${boxId} --silent --stderr-to-stdout --stdin=input.txt --run /usr/bin/python3 ${file}`,
121-
{ encoding: "utf-8" },
122-
);
123-
}
124-
resolve({ result: stdout.toString() });
125-
} catch (error: any) {
126-
const err = error.stdout.toString()
127-
? error.stdout.toString()
128-
: error.stderr.toString();
129-
resolve({ result: err });
139+
const childProcess = spawn(command, { shell: true });
140+
141+
// Handle process exit
142+
childProcess.on('exit', () => {
143+
const metaContent = readFileSync(metaFile, 'utf-8');
144+
const output = readFileSync(outputFile, 'utf-8');
145+
146+
const lines = metaContent.split('\n');
147+
const status = lines.find((line) => line.startsWith('status:'))?.split(':')[1]?.trim() || '';
148+
const exitCode = lines.find((line) => line.startsWith('exitCode:'))?.split(':')[1]?.trim() || '';
149+
150+
if (status === 'TO') {
151+
resolve({ result: 'Execution Time Out' });
152+
} else if (exitCode !== '0') {
153+
resolve({ result: output });
154+
} else {
155+
resolve({ result: output });
156+
}
157+
158+
});
159+
160+
} catch (error) {
161+
resolve({ result: 'error execute' });
130162
}
131163
});
132164
},

src/services/rabbitmq.service.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import client, { Connection, Channel, ConsumeMessage } from "amqplib";
22
import { environment } from "../config/environment";
3-
import { IResultSubmission, ISubmissionRequest } from "../interfaces/submission.interface";
3+
import { IResultSubmission, ISubmissionLearnify, ISubmissionRequest } from "../interfaces/submission.interface";
44
import {
55
ICompileRequest,
66
languageType,
@@ -41,6 +41,8 @@ export const rabbitMQService = {
4141
const channel: Channel = await connection.createChannel();
4242
await channel.assertQueue("compiler", { durable: true });
4343
await channel.assertQueue("submission", { durable: true });
44+
await channel.assertQueue("learnify-submission", { durable: true })
45+
4446
channel.consume("submission", async (msg: ConsumeMessage | null) => {
4547
try {
4648
if (!msg) {
@@ -86,6 +88,7 @@ export const rabbitMQService = {
8688
}
8789
channel.ack(msg);
8890
} catch (error) {
91+
console.log(error)
8992
throw new Error("Error receiveData");
9093
}
9194
});
@@ -120,5 +123,34 @@ export const rabbitMQService = {
120123
throw new Error("Error receiveData");
121124
}
122125
});
126+
127+
channel.consume("learnify-submission", async (msg: ConsumeMessage | null) => {
128+
try {
129+
if (!msg) {
130+
throw new Error("Invalid Incoming Message");
131+
}
132+
const submission: ISubmissionLearnify = JSON.parse(
133+
msg?.content.toString() as string,
134+
);
135+
const updateSourceCode = strip(submission.sourceCode);
136+
const updateFileName =
137+
submission.language === languageType.JAVA
138+
? submission.fileName
139+
: `${process.pid}`;
140+
submission.sourceCode = updateSourceCode;
141+
submission.fileName = updateFileName;
142+
const resultProblem = await resultService.outputResultWithTestCaseLeanify(submission)
143+
console.log(resultProblem)
144+
if ("status" in resultProblem) {
145+
// save to learnify database
146+
} else {
147+
throw new Error("Error To Submission File: " + resultProblem.result);
148+
}
149+
channel.ack(msg);
150+
} catch (error) {
151+
throw new Error("Error receiveData");
152+
}
153+
});
154+
123155
},
124156
};

0 commit comments

Comments
 (0)