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 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 4444920..e7cba4d 100644 --- a/src/app/(home)/_components/RunButton.tsx +++ b/src/app/(home)/_components/RunButton.tsx @@ -1,9 +1,73 @@ -import React from 'react' +"use client"; + +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 } = useCodeEditorState(); + const saveCodeExecution = useMutation(api.codeExecutions.saveCodeExecution); + const handleRun = async () => { + await runCode(); + const result = getExecutionResult(); + + if (user && result) { + //convex saving + await saveCodeExecution({ + language, + code: result.code, + output: result.output || undefined, + error: result.error || undefined, + }); + } + }; + 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..fbe8e55 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,94 @@ 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 }); + } + }, }; }); + + +export const getExecutionResult = () => useCodeEditorState.getState().executionResult;