From b150a377aea63a5c62b4bddcc3f39c626226face Mon Sep 17 00:00:00 2001 From: lakshaybabbar Date: Sat, 21 Sep 2024 16:11:03 +0530 Subject: [PATCH] Removed all compilers except python --- .dockerignore | 11 ---- Dockerfile | 21 ------- README.md | 41 +++++++++---- compiler/python/Dockerfile | 17 ++++++ compiler/python/app.py | 65 ++++++++++++++++++++ src/app/api/compile/python/route.js | 24 ++++++++ src/app/api/compile/route.js | 82 -------------------------- src/app/api/projects/[pid]/route.js | 7 +-- src/app/compiler/{ => python}/page.js | 46 +++++---------- src/components/Modals/CreateProject.js | 74 ++++++++++++++--------- src/components/Navbar/Navbar.js | 4 +- src/models/projects.js | 29 +++++---- 12 files changed, 219 insertions(+), 202 deletions(-) delete mode 100644 .dockerignore delete mode 100644 Dockerfile create mode 100644 compiler/python/Dockerfile create mode 100644 compiler/python/app.py create mode 100644 src/app/api/compile/python/route.js delete mode 100644 src/app/api/compile/route.js rename src/app/compiler/{ => python}/page.js (71%) diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 1952b32..0000000 --- a/.dockerignore +++ /dev/null @@ -1,11 +0,0 @@ -node_modules/* -.next/* -*.env -.git -.gitignore -.vscode -Dockerfile -docker-compose.yml -README.md -.dockerignore - diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 61e3195..0000000 --- a/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM node:20-slim - -WORKDIR /app - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - python3 python3-pip default-jdk build-essential gcc g++ && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -COPY package*.json ./ - -RUN npm ci - -COPY . . - -EXPOSE 3000 - -RUN npm run build - -CMD ["npm","start"] diff --git a/README.md b/README.md index 71cd8a1..76f6deb 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@ CodeFramer is a versatile code editor built to enhance your coding experience wi ## Tech Stack -- **FullStack Framework:** Next.js 14 +- **Frontend:** Next.js 14 +- **Backend** Next.js 14 API Routes, flask - **Database:** MongoDB - **Authentication:** Jose (JWT) - **Styling:** Tailwind CSS, ShadCn, Aceternity UI -- **Other Libraries:** React Query, Redux Toolkit, Nodemailer, React-Markdown, Monaco Editor, UUID, Bcryptjs, React Icons, Mongoose - +- **Other Libraries:** React Query, Redux Toolkit, Monaco Editor, React-Markdown, Bcryptjs, Mongoose ## Installation @@ -32,26 +32,45 @@ CodeFramer is a versatile code editor built to enhance your coding experience wi ```bash git clone https://github.com/lakshaybabbar/codeframer.git + ``` + 2. Navigate to the project directory: - ```bash + ```bash cd codeframer + ``` 3. Install dependencies: - ```bash + ```bash npm install + ``` 4. Configure Environment Variables: - - `ACCESS_SECRET_KEY`: User-defined access secret key - - `URI`: MongoDB URI address - - `BASE_URL`: Hosting address - - `NEXT_PUBLIC_AI_API`: Google AI Studio API -5. Start the development server: - ```bash + + - `ACCESS_SECRET_KEY`: User-defined access secret key + - `URI`: MongoDB URI address + - `BASE_URL`: Hosting address + - `NEXT_PUBLIC_AI_API`: Google AI Studio API + - `COMPILER_URL`: Path of python compiler + +5. Start docker container for compiling python code: + + ```bash + cd compiler/python + docker build -t pycompiler . + docker run -p 5000:5000 --name compiler pycompiler + ``` + +6. Start the development server: + ```bash npm run dev + ``` ## Contributing + We welcome contributions from the community! If you have ideas for improvements or bug fixes, please feel free to submit a pull request. ## Bug Reports + If you encounter any bugs or issues while using CodeFramer, please open an issue on GitHub with detailed information about the problem. ## License + CodeFramer is licensed under the MIT License. diff --git a/compiler/python/Dockerfile b/compiler/python/Dockerfile new file mode 100644 index 0000000..b88ba9a --- /dev/null +++ b/compiler/python/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.10-slim + +RUN adduser --disabled-password --gecos '' appuser + +WORKDIR /app + +COPY app.py . + +RUN pip install flask gunicorn RestrictedPython + +RUN chown -R appuser:appuser /app + +USER appuser + +EXPOSE 5000 + +CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"] \ No newline at end of file diff --git a/compiler/python/app.py b/compiler/python/app.py new file mode 100644 index 0000000..914e25d --- /dev/null +++ b/compiler/python/app.py @@ -0,0 +1,65 @@ +from flask import Flask, request, jsonify +import tempfile +import shutil +import re +import sys +import io + +app = Flask(__name__) + +DANGEROUS_KEYWORDS = [ + r'\bos\b', + r'\bsys\b', + r'\bimport\b', + r'\beval\b', + r'\bexec\b', + r'\bopen\b', + r'\bsubprocess\b' +] + +def is_code_safe(code): + + for pattern in DANGEROUS_KEYWORDS: + if re.search(pattern, code): + return False + return True + +@app.route('/execute', methods=['POST']) +def execute_python_safely(): + code = request.json.get('code') + inputs = request.json.get('inputs', []) + + if not code: + return jsonify({"error": "No code provided."}), 400 + + if not is_code_safe(code): + return jsonify({"error": "Code contains unsafe operations."}), 400 + + temp_dir = tempfile.mkdtemp(prefix="user_code_") + + try: + input_data = iter(inputs) + + output_capture = io.StringIO() + sys.stdout = output_capture # + + exec_env = { + 'input': lambda prompt: next(input_data, ''), + } + + exec(code, exec_env) + + output = output_capture.getvalue() + + return jsonify({ + "output": output, + "status": 0 + }) + except Exception as e: + return jsonify({"error": str(e)}), 500 + finally: + shutil.rmtree(temp_dir) + sys.stdout = sys.__stdout__ + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/src/app/api/compile/python/route.js b/src/app/api/compile/python/route.js new file mode 100644 index 0000000..b46ef15 --- /dev/null +++ b/src/app/api/compile/python/route.js @@ -0,0 +1,24 @@ +import { NextResponse } from "next/server"; + +export async function POST(req) { + try { + const { code, inputs } = await req.json(); + const response = await fetch(process.env.COMPILER_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ code, inputs }), + }); + const data = await response.json(); + if (data.error) { + throw new Error(data.error); + } + if (!response.ok) { + throw new Error("Failed to compile code"); + } + return NextResponse.json(data, { status: 200 }); + } catch (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/src/app/api/compile/route.js b/src/app/api/compile/route.js deleted file mode 100644 index 1c0dac1..0000000 --- a/src/app/api/compile/route.js +++ /dev/null @@ -1,82 +0,0 @@ -import { exec } from "child_process"; -import fs from "node:fs/promises"; -import path from "node:path"; -import { NextResponse } from "next/server"; - -const languages = [ - { name: "python", extension: "py", command: "python3" }, - { name: "javascript", extension: "js", command: "node" }, - { name: "java", extension: "java", command: "java" }, - { name: "c", extension: "c", command: "gcc", output: "a.out" }, - { name: "cpp", extension: "cpp", command: "g++", output: "a.out" }, -]; - -export async function POST(req) { - try { - const { code, language, inputs } = await req.json(); - - const lang = languages.find((lang) => lang.name === language); - if (!lang) { - return NextResponse.json( - { error: "Language not supported" }, - { status: 400 } - ); - } - - const tempDirName = Math.random().toString(36).substring(6); - const tempDirPath = path.join(process.cwd(), "temp", tempDirName); - const filePath = path.join(tempDirPath, `temp.${lang.extension}`); - - try { - await fs.mkdir(tempDirPath, { recursive: true }); - await fs.writeFile(filePath, code); - - const { stdout, stderr } = await execCommand( - `${lang.command} ${filePath} ${lang.output && `&& ./${lang.output}`}`, - tempDirPath, - inputs - ); - - return NextResponse.json({ output: stdout }); - } catch (error) { - if (error.stderr) { - return NextResponse.json( - { stderr: error.stderr || "Execution error" }, - { status: 400 } - ); - } - return NextResponse.json({ error: error.message }, { status: 500 }); - } finally { - await fs.rm(tempDirPath, { recursive: true, force: true }); - } - } catch (error) { - return NextResponse.json({ error: error.message }, { status: 500 }); - } -} - -const execCommand = (cmd, cwd, inputs) => { - return new Promise((resolve, reject) => { - const process = exec(cmd, { cwd }, (error, stdout, stderr) => { - if (error) { - reject({ stderr }); - } else { - resolve({ stdout, stderr }); - } - }); - - if (inputs) { - process.stdin.write(inputs); - process.stdin.end(); - } - - const timer = setTimeout(() => { - process.kill("SIGTERM"); - fs.rm(cwd, { recursive: true, force: true }); - reject({ stderr: "Execution timeout exceeded 10 seconds" }); - }, 10000); - - process.on("exit", () => { - clearTimeout(timer); - }); - }); -}; diff --git a/src/app/api/projects/[pid]/route.js b/src/app/api/projects/[pid]/route.js index 3888d59..8b49b2d 100644 --- a/src/app/api/projects/[pid]/route.js +++ b/src/app/api/projects/[pid]/route.js @@ -12,7 +12,7 @@ export async function GET(req, { params }) { const projectData = await Project.findOne({ _id: pid, userId: authData.id, - }).select("html css js _id"); + }).select("-userId -__v -createdAt -updatedAt"); if (!projectData) { return NextResponse.json( { @@ -59,7 +59,6 @@ export async function PUT(req, { params }) { try { const { pid } = params; const reqBody = await req.json(); - const { html, css, js } = await reqBody; const Headers = headers(); const authData = await JSON.parse(Headers.get("authData")); const projectData = await Project.findOne({ @@ -74,9 +73,7 @@ export async function PUT(req, { params }) { { status: 404 } ); } - projectData.html = html; - projectData.css = css; - projectData.js = js; + projectData[reqBody] = reqBody; await projectData.save(); return NextResponse.json({ message: "Project is updated successfully", diff --git a/src/app/compiler/page.js b/src/app/compiler/python/page.js similarity index 71% rename from src/app/compiler/page.js rename to src/app/compiler/python/page.js index bba506a..dcdc572 100644 --- a/src/app/compiler/page.js +++ b/src/app/compiler/python/page.js @@ -3,20 +3,12 @@ import { Editor } from "@monaco-editor/react"; import { useState, useRef } from "react"; import { Button } from "@/components/ui/button"; import { useToast } from "@/components/ui/use-toast"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; export default function Compiler() { const editorRef = useRef(); - const [language, setLanguage] = useState("python"); const [code, setCode] = useState(""); const [output, setOutput] = useState(""); - const [outerr, setOuterr] = useState(""); + const [status, setStatus] = useState(0); const [loading, setLoading] = useState(false); const [inputs, setInputs] = useState(""); const { toast } = useToast(); @@ -28,27 +20,29 @@ export default function Compiler() { const handleSubmit = async () => { setOutput(""); - setOuterr(""); + setStatus(0); setLoading(true); try { - const res = await fetch("/api/compile", { + const res = await fetch("/api/compile/python", { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ code, language, inputs }), + body: JSON.stringify({ + code, + inputs: inputs.split("\n"), + }), signal: new AbortController().signal, }); const data = await res.json(); if (data.error) { throw new Error(data.error); - } else if (data.stderr) { - setOuterr(data.stderr); } else if (!res.ok) { throw new Error("Something went wrong"); } else { setOutput(data.output); + setStatus(data.status); } setLoading(false); @@ -66,19 +60,7 @@ export default function Compiler() { return (
-
- +
-
-          {output} {outerr && {outerr}}
+        
+          {output}
         

Enter Your Inputs

diff --git a/src/components/Modals/CreateProject.js b/src/components/Modals/CreateProject.js index e1db652..06c9652 100644 --- a/src/components/Modals/CreateProject.js +++ b/src/components/Modals/CreateProject.js @@ -5,13 +5,19 @@ import { createPortal } from "react-dom"; import { useSelector } from "react-redux"; import { useRouter } from "next/navigation"; import useSend from "@/hooks/useSend"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; const CreateProject = ({ isOpen, setIsOpen }) => { const ref = useRef(null); - const [data, setData] = useState({ - name: "", - description: "", - }); + const [name, setName] = useState(""); + const [environment, setEnvironment] = useState("web"); + const [language, setLanguage] = useState(""); const { isAuth } = useSelector((state) => state.auth); const navigate = useRouter(); const { fetchData, isError, error, loading, setIsError } = useSend(); @@ -24,48 +30,62 @@ const CreateProject = ({ isOpen, setIsOpen }) => { ref.current = document.getElementById("modal"); } !isOpen && setIsError(false); - }, [isOpen, isAuth, navigate]); + }, [isOpen, isAuth, navigate, setIsOpen, setIsError]); const dataHandler = (e) => { - const { name, value } = e.target; - setData((prev) => { - return { ...prev, [name]: value }; - }); + setName(e.target.value); setIsError(false); }; const projectHandler = async (e) => { e.preventDefault(); - const res = await fetchData("/api/projects/create", "POST", data); + const res = await fetchData("/api/projects/create", "POST", { + name, + type: environment, + }); if (res && res.success) { setIsOpen(false); } - setData({ - name: "", - description: "", - }); - res.success && navigate.push(`/web-editor/${res.pid}`); + res.success && + navigate.push( + environment === "web" ? `/web-editor/${res.pid}` : `/compiler/python` + ); }; return isOpen && isAuth && ref.current ? createPortal( -
-
-

Create New

-

Web Environment

+
+
+

Create New

- +
+ + {environment === "compiler" && ( + + )} +
+

{isError && error}