Skip to content
Open
13 changes: 7 additions & 6 deletions backend/code-execution-service/src/utils/code-runner.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@ export async function runCode(
const folder = `./code/${Date.now()}`;
await fs.mkdir(folder, { recursive: true });

const sourceFile = `${folder}/solution.${extensions[lang]}`;
const inputFile = `${folder}/input.txt`;
// Create a ran between 1 and 100
const rand = Math.floor(Math.random() * 100) + 1;

const sourceFileName = `solution`;
const sourceFile = `${folder}/${sourceFileName}.${extensions[lang]}`;
const inputFile = `${folder}/input-${rand}.txt`;
const outputFile = `${folder}/output.txt`;

await fs.writeFile(sourceFile, code);
await fs.writeFile(inputFile, input);

// Input -> Generates output
// Compare generated output with expected output

let compileCommand = '';
let runCommand = `timeout ${timeout} `;

if (lang === 'java') {
compileCommand = `javac ${sourceFile}`;
runCommand += `java -cp ${folder} main < ${inputFile}`;
runCommand += `java -cp ${folder} ${sourceFileName} < ${inputFile}`;
} else if (lang === 'python3') {
runCommand += `python3 ${sourceFile} < ${inputFile}`;
}
Expand Down
1 change: 1 addition & 0 deletions backend/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ services:
- "3005:4000"

code-execution-service:
read_only: true
build:
context: ./code-execution-service
target: development
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const SESSION_JOIN = 'sessionJoin';
export const SESSION_LEAVE = 'sessionLeave';
export const SESSION_END = 'sessionEnd';
export const SUBMIT = 'submit';
export const CHANGE_LANGUAGE = 'changeLanguage';

// Chat messages
export const CHAT_SEND_MESSAGE = 'chatSendMessage';
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { Server, Socket } from 'socket.io';
import { JoinCollabSessionRequestDto } from './dto/join-collab-session-request.dto';
import {
CHANGE_LANGUAGE,
CHAT_SEND_MESSAGE,
SESSION_JOIN,
SESSION_LEAVE,
Expand All @@ -17,6 +18,7 @@ import {
import {
CHAT_RECIEVE_MESSAGE,
EXCEPTION,
LANGUAGE_CHANGED,
SESSION_ERROR,
SESSION_JOINED,
SESSION_LEFT,
Expand Down Expand Up @@ -45,6 +47,8 @@ export class CollaborationGateway implements OnGatewayDisconnect {
private socketUserMap = new Map<string, string>(); // socketId -> userId
private userSocketMap = new Map<string, string>(); // userId -> socktId

private sessionLanguageMap = new Map<string, string>(); // sessionId -> language

constructor(
@Inject('QUESTION_SERVICE') private questionService: ClientProxy,
@Inject('CODE_EXECUTION_SERVICE') private codeExecutionService: ClientProxy,
Expand Down Expand Up @@ -92,13 +96,24 @@ export class CollaborationGateway implements OnGatewayDisconnect {
console.log('sessionjoin and messages retrieved:');
console.log(messages);

const existingLanguage = this.sessionLanguageMap.get(sessionId);
this.sessionLanguageMap.set(sessionId, existingLanguage || 'python3');

// emit joined event
this.server.to(sessionId).emit(SESSION_JOINED, {
userId, // the user who recently joined
sessionId,
messages, // chat messages
language: existingLanguage || 'python3', // default language
sessionUserProfiles, // returns the all session member profiles
});

return {
success: true,
data: {
messages, // chat messages
}
};
} catch (e) {
console.log(e);
return {
Expand Down Expand Up @@ -198,10 +213,11 @@ export class CollaborationGateway implements OnGatewayDisconnect {
sessionId: string;
questionId: string;
code: string;
language: string;
},
) {
try {
const { userId, sessionId, questionId, code } = payload;
const { userId, sessionId, questionId, code, language } = payload;

if (!userId || !sessionId || !code) {
client.emit(SESSION_ERROR, 'Invalid submit request payload.');
Expand Down Expand Up @@ -229,7 +245,7 @@ export class CollaborationGateway implements OnGatewayDisconnect {
{
code: code,
input: testCase.input,
language: 'python3',
language: language,
timeout: 5, // TODO: update this to the correct timeout, default is 5 seconds
},
),
Expand Down Expand Up @@ -284,6 +300,36 @@ export class CollaborationGateway implements OnGatewayDisconnect {
}
}

@SubscribeMessage(CHANGE_LANGUAGE)
async handleChangeLanguage(
@ConnectedSocket() client: Socket,
@MessageBody()
payload: {
userId: string;
sessionId: string;
language: string;
},
) {
try {
const { userId, sessionId, language } = payload;

if (!userId || !sessionId || !language) {
client.emit(SESSION_ERROR, 'Invalid change language request payload.');
return;
}

this.sessionLanguageMap.set(sessionId, language);

this.server.to(sessionId).emit(LANGUAGE_CHANGED, {
changedBy: userId,
language,
});
} catch (error) {
client.emit(EXCEPTION, `Error changing language: ${error.message}`);
return;
}
}

handleDisconnect(@ConnectedSocket() client: Socket) {
// When client disconnects from the socket
console.log(`User: ${this.socketUserMap.get(client.id)} disconnected`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const SESSION_ERROR = 'sessionError';
export const EXCEPTION = 'exception';
export const SUBMITTING = 'submitting';
export const SUBMITTED = 'submitted';
export const LANGUAGE_CHANGED = 'languageChanged';

// Chat events
export const CHAT_RECIEVE_MESSAGE = 'chatReceiveMessage';
2 changes: 2 additions & 0 deletions backend/gateway-service/src/modules/match/match.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,14 @@ export class MatchGateway implements OnGatewayInit {
matchId,
matchUserId: user2,
matchUsername: user2Details.username,
matchDisplayname: user2Details.displayName,
});
this.server.to(user2SocketId).emit(MATCH_FOUND, {
message: `You have found a match`,
matchId,
matchUserId: user1,
matchUsername: user1Details.username,
matchDisplayname: user1Details.displayName,
});

// Store participants for this matchId
Expand Down
42 changes: 21 additions & 21 deletions frontend/src/app/collaboration/[sessionId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,27 +63,27 @@ export default async function Page(props: { params: Params }) {
socketUrl={socketUrl}
>
<div className="flex flex-row w-full h-full overflow-hidden">
<ResizablePanelGroup
className="flex w-full h-full"
direction="horizontal"
>
<ResizablePanel className="p-1" defaultSize={30}>
<QuestionTabPanel question={question} />
</ResizablePanel>

<ResizableHandle withHandle={true} />

<ResizablePanel defaultSize={70}>
<CenterPanel question={question} />
</ResizablePanel>
</ResizablePanelGroup>

{chatFeature && (
<div className="flex p-1">
<Chatbox />
</div>
)}
</div>
<ResizablePanelGroup
className="flex w-full h-full"
direction="horizontal"
>
<ResizablePanel className="p-1" defaultSize={30}>
<QuestionTabPanel question={question} />
</ResizablePanel>

<ResizableHandle withHandle={true} />

<ResizablePanel defaultSize={70}>
<CenterPanel question={question} />
</ResizablePanel>
</ResizablePanelGroup>

{chatFeature && (
<div className="flex p-1">
<Chatbox />
</div>
)}
</div>
</SessionProvider>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ export default function ChatBottomToolbar({

const { handleSubmit, formState, reset } = methods;

const onSubmit = useCallback(async (data: z.infer<typeof FormSchema>) => {
if (formState.isSubmitting || data.message.length === 0) return;
handleSendMessage(data.message);
reset({ message: "" });
}, []);
const onSubmit = useCallback(
async (data: z.infer<typeof FormSchema>) => {
if (formState.isSubmitting || data.message.length === 0) return;
handleSendMessage(data.message);
reset({ message: "" });
},
[formState.isSubmitting, handleSendMessage, reset]
);

return (
<CardFooter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ function ChatBubble({ message }: ChatBubbleProps) {

const profileDetails = useMemo(() => {
return getUserProfileDetailByUserId(message.userId);
}, [message]);
}, [message, getUserProfileDetailByUserId]);

const isSender = useMemo(() => {
return message.userId === userProfile?.id;
}, []);
}, [message.userId, userProfile?.id]);

const statusIcon = useMemo(() => {
switch (message.status) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Code, Pencil } from "lucide-react";
import TabPanel, { Tab } from "@/app/collaboration/_components/TabPanel";
import CollaborativeEditorTab from "./CollaborativeEditor/CollaborativeEditorTab";
import CollaborativeWhiteboardTab from "./CollaborativeWhiteboard/CollaborativeWhiteboardTab";
import LanguageSelector from "./CollaborativeEditor/LanguageSelector";

export function CollabCodePanel() {
const tabs: Tab[] =
Expand Down Expand Up @@ -29,5 +30,11 @@ export function CollabCodePanel() {
},
];

return <TabPanel tabs={tabs} defaultValue={"code"} />;
return (
<TabPanel
tabs={tabs}
defaultValue={"code"}
tabChildren={<LanguageSelector className="ml-auto" />}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,32 @@ import { useSessionContext } from "@/contexts/SessionContext";
import { Button } from "@/components/ui/button";
import { LoadingSpinner } from "@/components/LoadingSpinner";

const defaultEditorValues: { [key: string]: string } = {
python3: "# Start coding here",
java: "public class solution {\n public static void main(String[] args) {\n // Start coding here\n }\n}",
};

const editorLanguageMap: { [key: string]: string } = {
python3: "python",
java: "java",
};

interface CollaborativeEditorProps {
sessionId: string;
currentUser: UserProfile;
socketUrl?: string;
language?: string;
themeName?: string;
}

export default function CollaborativeEditor({
sessionId,
currentUser,
socketUrl = "ws://localhost:4001",
language = "python",
themeName = "clouds-midnight",
}: CollaborativeEditorProps) {
const { codeReview, submitCode, submitting } = useSessionContext();
const { codeReview, language, submitCode, submitting } = useSessionContext();
const { setCurrentClientCode } = codeReview;

const [editorRef, setEditorRef] = useState<editor.IStandaloneCodeEditor>();
const [provider, setProvider] = useState<WebsocketProvider>();

Expand All @@ -40,7 +49,7 @@ export default function CollaborativeEditor({
if (!editorRef) return;

const yDoc = new Y.Doc();
const yTextInstance = yDoc.getText("monaco");
const yTextInstance = yDoc.getText(`code-${language}`);
const yProvider = new WebsocketProvider(
`${socketUrl}/yjs?sessionId=${sessionId}&userId=${currentUser.id}`,
`c_${sessionId}`,
Expand Down Expand Up @@ -68,7 +77,14 @@ export default function CollaborativeEditor({
yDoc.destroy();
binding.destroy();
};
}, [sessionId, currentUser, socketUrl, editorRef, setCurrentClientCode]);
}, [
sessionId,
currentUser,
socketUrl,
editorRef,
setCurrentClientCode,
language,
]);

const handleEditorOnMount = useCallback(
(e: editor.IStandaloneCodeEditor, monaco: Monaco) => {
Expand All @@ -84,7 +100,7 @@ export default function CollaborativeEditor({
return (
<div className="relative w-full h-full min-h-24 min-w-24">
<Button
className="absolute bottom-10 right-8 z-10"
className="absolute z-10 bottom-10 right-8"
onClick={submitCode}
disabled={submitting}
>
Expand All @@ -108,11 +124,17 @@ export default function CollaborativeEditor({
onMount={handleEditorOnMount}
className="w-full h-full"
theme={themeName}
language={language}
language={editorLanguageMap[language]}
options={{
tabSize: 4,
padding: { top: 20 },
minimap: { enabled: false },
}}
defaultValue={defaultEditorValues[language]}
// value={code[language]}
// onChange={(value) => {
// setCode((prevCode) => ({ ...prevCode, [language]: value || "" }));
// }}
/>
</div>
);
Expand Down
Loading