From 596e566623504615bee80a81f7fc0e65fa846584 Mon Sep 17 00:00:00 2001 From: ARYPROGRAMMER Date: Tue, 17 Dec 2024 20:02:42 +0530 Subject: [PATCH 1/3] feat: code exec engine-piston --- src/app/(home)/_components/RunButton.tsx | 61 +++++++++++++++-- src/store/useCodeEditorStore.tsx | 87 ++++++++++++++++++++++-- 2 files changed, 139 insertions(+), 9 deletions(-) diff --git a/src/app/(home)/_components/RunButton.tsx b/src/app/(home)/_components/RunButton.tsx index 4444920..19e7540 100644 --- a/src/app/(home)/_components/RunButton.tsx +++ b/src/app/(home)/_components/RunButton.tsx @@ -1,9 +1,62 @@ -import React from 'react' +"use client"; + +import { useCodeEditorState } from "@/store/useCodeEditorStore"; +import { useUser } from "@clerk/nextjs"; +import { Loader2, Play } from "lucide-react"; +import React from "react"; +import { motion } from "framer-motion"; function RunButton() { + const { user } = useUser(); + const { runCode, language, isRunning, executionResult } = + useCodeEditorState(); + + const handleRun = async () => { + await runCode(); + + if (user && executionResult) { + //convex saving + } + }; + return ( -
RunBuilding...
- ) + +
+ +
+ {isRunning ? ( + <> +
+ +
+
+ + Executing... + + + ) : ( + <> +
+ +
+ + Execute Code + + + )} +
+ + ); } -export default RunButton \ No newline at end of file +export default RunButton; diff --git a/src/store/useCodeEditorStore.tsx b/src/store/useCodeEditorStore.tsx index 57f164e..8520a3d 100644 --- a/src/store/useCodeEditorStore.tsx +++ b/src/store/useCodeEditorStore.tsx @@ -2,6 +2,7 @@ import { create } from "zustand"; import { LANGUAGE_CONFIG } from "@/app/(home)/_constants"; import { Monaco } from "@monaco-editor/react"; import { CodeEditorState } from "@/types"; +import { version } from "os"; const getInitialState = () => { //initial load @@ -51,7 +52,7 @@ export const useCodeEditorState = create((set, get) => { set({ fontSize }); }, - setLanguage: (language: string)=>{ + setLanguage: (language: string) => { const currentCode = get().editor?.getValue(); if (currentCode) { localStorage.setItem(`editor-code-${get().language}`, currentCode); @@ -59,15 +60,91 @@ export const useCodeEditorState = create((set, get) => { localStorage.setItem("editor-language", language); set({ language, - output:"", - error:null, + output: "", + error: null, }); }, runCode: async () => { + const { language, getCode } = get(); + const code = getCode(); + if (!code) { + set({ error: "Code is empty" }); + return; + } - //TODO : Implement code execution + set({ isRunning: true, error: null, output: "" }); - }, + try { + const runtime = LANGUAGE_CONFIG[language].pistonRuntime; + const res = await fetch("https://emkc.org/api/v2/piston/execute", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + language: runtime.language, + version: runtime.version, + files: [{ content: code }], + }), + }); + const data = await res.json(); + console.log("data from piston ", data); + if (data.message) { + set({ + error: data.message, + executionResult: { code, output: "", error: data.message }, + }); + + return; + } + + // error handling + + if (data.compile && data.compile.code !== 0) { + const error = data.compile.stderr || data.compile.output; + set({ + error, + executionResult: { + code, + output: "", + error, + }, + }); + return; + } + if (data.run && data.run.code !== 0) { + const error = data.run.stderr || data.run.output; + set({ + error, + executionResult: { + code, + output: "", + error, + }, + }); + return; + } + + const output = data.run.output; + set({ + output: output.trim(), + executionResult: { code, output: output.trim(), error: null }, + }); + } catch (error) { + console.log("error from piston ", error); + set({ + error: "ERROR RUNNING CODE", + + executionResult: { + code, + output: "", + error: "ERROR RUNNING CODE", + }, + }); + } finally { + set({ isRunning: false }); + } + }, }; }); From 249407485598b8dee9256943838dff3c0ca60948 Mon Sep 17 00:00:00 2001 From: ARYPROGRAMMER Date: Wed, 18 Dec 2024 01:58:31 +0530 Subject: [PATCH 2/3] feat: convex updated for exec mutation --- convex/_generated/api.d.ts | 2 ++ convex/codeExecutions.ts | 35 ++++++++++++++++++++++++ src/app/(home)/_components/RunButton.tsx | 21 ++++++++++---- src/store/useCodeEditorStore.tsx | 3 ++ 4 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 convex/codeExecutions.ts diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index f16871a..5c88e74 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -13,6 +13,7 @@ import type { FilterApi, FunctionReference, } from "convex/server"; +import type * as codeExecutions from "../codeExecutions.js"; import type * as http from "../http.js"; import type * as users from "../users.js"; @@ -25,6 +26,7 @@ import type * as users from "../users.js"; * ``` */ declare const fullApi: ApiFromModules<{ + codeExecutions: typeof codeExecutions; http: typeof http; users: typeof users; }>; diff --git a/convex/codeExecutions.ts b/convex/codeExecutions.ts new file mode 100644 index 0000000..4e3f371 --- /dev/null +++ b/convex/codeExecutions.ts @@ -0,0 +1,35 @@ +import { ConvexError, v } from "convex/values"; +import { mutation } from "./_generated/server"; + +export const saveCodeExecution = mutation({ + args: { + language: v.string(), + code: v.string(), + output: v.optional(v.string()), + error: v.optional(v.string()), + }, + + handler: async (ctx, args) => { + const identity = await ctx.auth.getUserIdentity(); + if (!identity) { + throw new ConvexError("User not authenticated"); + } + + const user = await ctx.db + .query("users") + .withIndex("by_user_id") + .filter((q) => q.eq(q.field("userId"), identity.subject)) + .first(); + + if (!user?.isPro && args.language !== "javascript") { + throw new ConvexError( + "Only Pro users can execute code in languages other than JavaScript" + ); + } + + await ctx.db.insert("codeExecutions", { + ...args, + userId: identity.subject, + }); + }, +}); diff --git a/src/app/(home)/_components/RunButton.tsx b/src/app/(home)/_components/RunButton.tsx index 19e7540..e7cba4d 100644 --- a/src/app/(home)/_components/RunButton.tsx +++ b/src/app/(home)/_components/RunButton.tsx @@ -1,21 +1,32 @@ "use client"; -import { useCodeEditorState } from "@/store/useCodeEditorStore"; +import { + getExecutionResult, + useCodeEditorState, +} from "@/store/useCodeEditorStore"; import { useUser } from "@clerk/nextjs"; import { Loader2, Play } from "lucide-react"; import React from "react"; import { motion } from "framer-motion"; +import { useMutation } from "convex/react"; +import { api } from "../../../../convex/_generated/api"; function RunButton() { const { user } = useUser(); - const { runCode, language, isRunning, executionResult } = - useCodeEditorState(); - + const { runCode, language, isRunning } = useCodeEditorState(); + const saveCodeExecution = useMutation(api.codeExecutions.saveCodeExecution); const handleRun = async () => { await runCode(); + const result = getExecutionResult(); - if (user && executionResult) { + if (user && result) { //convex saving + await saveCodeExecution({ + language, + code: result.code, + output: result.output || undefined, + error: result.error || undefined, + }); } }; diff --git a/src/store/useCodeEditorStore.tsx b/src/store/useCodeEditorStore.tsx index 8520a3d..fbe8e55 100644 --- a/src/store/useCodeEditorStore.tsx +++ b/src/store/useCodeEditorStore.tsx @@ -148,3 +148,6 @@ export const useCodeEditorState = create((set, get) => { }, }; }); + + +export const getExecutionResult = () => useCodeEditorState.getState().executionResult; From 7f852b338aafe8ecc8638b1db1da181b87ad221c Mon Sep 17 00:00:00 2001 From: ARYPROGRAMMER Date: Wed, 18 Dec 2024 02:00:14 +0530 Subject: [PATCH 3/3] chore: remove PR labeler --- .github/workflows/label.yml | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .github/workflows/label.yml diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml deleted file mode 100644 index 0854308..0000000 --- a/.github/workflows/label.yml +++ /dev/null @@ -1,23 +0,0 @@ -# This workflow will triage pull requests and apply a label based on the -# paths that are modified in the pull request. -# -# To use this workflow, you will need to set up a .github/labeler.yml -# file with configuration. For more information, see: -# https://github.com/actions/labeler - -name: Labeler -on: [pull_request_target] - -jobs: - label: - - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - - steps: - - uses: actions/labeler@v4 - with: - repo-token: "${{ secrets.GH_TOKEN}}" - configuration-path: labeler.yml