Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Final touch ups #152

Merged
merged 7 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion backend/collaboration-service/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import "dotenv/config";
import express from "express";
import express, { Request, Response } from "express";
import { createServer } from "http";
import { WebSocketServer } from "ws";
import { Server } from "socket.io";
Expand All @@ -13,10 +13,12 @@ import {
import cors from "cors";
import { EditorLanguageEnum } from "./types/languages";
import pair from "./models/pair";
import axios from "axios";

const FRONTEND_URL = process.env.FRONTEND_URL as string;
const app = express();
app.use(cors({ origin: FRONTEND_URL, credentials: true }));
app.use(express.json());

const SOCKET_IO_PORT =
(process.env.SOCKET_IO_PORT as string) || (process.env.PORT as string);
Expand Down Expand Up @@ -68,10 +70,57 @@ httpServer.listen(SOCKET_IO_PORT, () => {
);
});

// Code Execution
app.get(
"/api/code-execute/:token",
async (req: Request, res: Response) => {
try {
const token = req.params.token;
const url = `https://${process.env.REACT_APP_RAPID_API_HOST}/submissions/${token}`;
const response = await axios.get(url, {
params: { base64_encoded: 'true', fields: '*' },
headers: {
'X-RapidAPI-Host': process.env.REACT_APP_RAPID_API_HOST,
'X-RapidAPI-Key': process.env.REACT_APP_RAPID_API_KEY,
},
});
res.send(response.data);
} catch (err) {
res.json({
errors: [{ msg: "Something went wrong." }],
});
}
},
);
app.post(
"/api/code-execute",
async (req: Request, res: Response) => {
try {
const url = `https://${process.env.REACT_APP_RAPID_API_HOST}/submissions`;
const response = await axios.post(url, req.body, {
params: { base64_encoded: 'true', fields: '*' },
headers: {
'content-type': 'application/json',
'Content-Type': 'application/json',
'X-RapidAPI-Host': process.env.REACT_APP_RAPID_API_HOST,
'X-RapidAPI-Key': process.env.REACT_APP_RAPID_API_KEY,
},
});
const token = response.data.token as string;
res.send(token);
} catch (err) {
res.json({
errors: [{ msg: "Something went wrong." }],
});
}
},
);

app.get("/api/check-authorization", checkAuthorisedUser);
app.get("/api/get-first-question", getFirstQuestion);
app.get("/api/get-second-question", getSecondQuestion);
app.get("/api/get-pair-ids", getPairIds);

interface RoomLanguages {
[roomId: string]: EditorLanguageEnum;
}
Expand Down
3 changes: 2 additions & 1 deletion backend/user-service/src/controllers/authController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,10 +324,11 @@ export const oAuthNewUser: RequestHandler[] = [
}

try {
const hashedPassword = hashPassword(randomPassword());
const newUser = await prisma.user.create({
data: {
username: username,
password: randomPassword(),
password: hashedPassword,
email: email,
role: Role.USER,
githubId: githubUserId,
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export const forumServiceClient = axios.create({
});

export const codeExecutionServiceClient = axios.create({
baseURL: `https://${process.env.REACT_APP_RAPID_API_HOST}`,
baseURL: process.env.REACT_APP_COLLABORATION_SERVICE_SOCKET_IO_BACKEND_URL,
withCredentials: true,
});

export default client;
22 changes: 4 additions & 18 deletions frontend/src/api/code/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,17 @@ import { codeExecutionServiceClient } from '../base';

export default class ExecutorAPI {
protected getExecutorUrl(): string {
return '/submissions';
return '/api/code-execute';
}

public async submitCode(codeSubmission: ExecutorSubmissionPostData): Promise<string> {
const response = await codeExecutionServiceClient.post(this.getExecutorUrl(), codeSubmission, {
params: { base64_encoded: 'true', fields: '*' },
headers: {
'content-type': 'application/json',
'Content-Type': 'application/json',
'X-RapidAPI-Host': process.env.REACT_APP_RAPID_API_HOST,
'X-RapidAPI-Key': process.env.REACT_APP_RAPID_API_KEY,
},
});
const token = response.data.token as string;
const response = await codeExecutionServiceClient.post(this.getExecutorUrl(), codeSubmission);
const token = response.data as string;
return token;
}

public async getExecutorSubmissionData(token: string): Promise<ExecutorStatusData> {
const response = await codeExecutionServiceClient.get(`${this.getExecutorUrl()}/${token}`, {
params: { base64_encoded: 'true', fields: '*' },
headers: {
'X-RapidAPI-Host': process.env.REACT_APP_RAPID_API_HOST,
'X-RapidAPI-Key': process.env.REACT_APP_RAPID_API_KEY,
},
});
const response = await codeExecutionServiceClient.get(`${this.getExecutorUrl()}/${token}`);
const submissionStatus = response.data as ExecutorStatusData;
return submissionStatus;
}
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/content/PasswordField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ import { BiShow, BiHide } from 'react-icons/bi';
interface PasswordFieldProps {
placeholder?: string;
onChange?: React.ChangeEventHandler<HTMLInputElement>;
required?: boolean;
}

const PasswordField: React.FC<PasswordFieldProps> = ({
placeholder = 'Enter password',
onChange,
required,
}: PasswordFieldProps) => {
const [isVisible, setVisibility] = useState(false);
return (
<InputGroup size="md">
<Input type={isVisible ? 'text' : 'password'} placeholder={placeholder} onChange={onChange} />
<Input type={isVisible ? 'text' : 'password'} placeholder={placeholder} onChange={onChange} required={required} />
<InputRightElement width="4.5rem">
<IconButton
aria-label={isVisible ? 'Hide password' : 'Show Password'}
Expand Down
12 changes: 11 additions & 1 deletion frontend/src/components/navigation/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Link } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from '../../reducers/hooks';
import { logOut, selectIsLoggedIn } from '../../reducers/authSlice';
import AuthAPI from '../../api/users/auth';
import ConfirmationDialog from '../content/ConfirmationDialog';

const Navbar: React.FC = () => {
const { colorMode, toggleColorMode } = useColorMode();
Expand Down Expand Up @@ -43,7 +44,16 @@ const Navbar: React.FC = () => {
</Link>
)}
<Button onClick={toggleColorMode}>{colorMode === 'light' ? <MoonIcon /> : <SunIcon />}</Button>
{isLoggedIn && <Button onClick={handleLogout}>Log Out</Button>}
{isLoggedIn && (
<ConfirmationDialog
dialogHeader="Goodbye?"
dialogBody="Do you want to log out?"
mainButtonLabel="Log Out"
rightButtonLabel="Log Out"
onConfirm={handleLogout}
mainButtonProps={{ size: 'md', mt: 0, padding: 2.5 }}
/>
)}
</HStack>
</Flex>
</Box>
Expand Down
50 changes: 44 additions & 6 deletions frontend/src/components/questions/QuestionDeleteIconButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { CloseIcon } from '@chakra-ui/icons';
import { Tooltip, IconButton, useToast } from '@chakra-ui/react';
import {
Tooltip,
IconButton,
useToast,
AlertDialog,
AlertDialogBody,
AlertDialogCloseButton,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
Button,
useDisclosure,
} from '@chakra-ui/react';
import React from 'react';
import QuestionsAPI from '../../api/questions/questions';

Expand All @@ -12,6 +25,8 @@ const QuestionDeleteIconButton: React.FC<QuestionDeleteIconButtonProps> = ({
questionId,
onDelete,
}: QuestionDeleteIconButtonProps) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const cancelRef = React.useRef<HTMLButtonElement>(null);
const toast = useToast();
const handleDelete = (): void => {
new QuestionsAPI()
Expand All @@ -33,11 +48,34 @@ const QuestionDeleteIconButton: React.FC<QuestionDeleteIconButtonProps> = ({
};

return (
<Tooltip label={`Delete Question ${questionId}`}>
<IconButton aria-label="Delete Question" onClick={handleDelete} _hover={{ bg: 'transparent' }} bg="transparent">
<CloseIcon boxSize="3" />
</IconButton>
</Tooltip>
<>
<Tooltip label={`Delete Question ${questionId}`}>
<IconButton aria-label="Delete Question" onClick={onOpen} _hover={{ bg: 'transparent' }} bg="transparent">
<CloseIcon boxSize="3" />
</IconButton>
</Tooltip>
<AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}>
<AlertDialogOverlay>
<AlertDialogContent minW={{ lg: '700px' }} padding={2}>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
Delete question?
</AlertDialogHeader>
<AlertDialogCloseButton />
<AlertDialogBody>
Are you sure you want to delete question {questionId}? This action is irreversible!
</AlertDialogBody>
<AlertDialogFooter mt={2}>
<Button ref={cancelRef} onClick={onClose}>
Cancel
</Button>
<Button onClick={handleDelete} ml={3} colorScheme="blue">
Delete Question
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
);
};

Expand Down
6 changes: 3 additions & 3 deletions frontend/src/pages/auth/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import AuthAPI from '../../api/users/auth';
import type { AxiosError } from 'axios';
import { useAppDispatch } from '../../reducers/hooks';
import { setUser } from '../../reducers/authSlice';
import PasswordField from '../../components/content/PasswordField';

const LoginForm: React.FC = () => {
const GITHUB_OAUTH_CLIENT_ID = process.env.REACT_APP_GITHUB_OAUTH_CLIENT_ID;
Expand Down Expand Up @@ -74,9 +75,8 @@ const LoginForm: React.FC = () => {
</FormControl>
<FormControl isRequired>
<FormLabel>Password</FormLabel>
<Input
type="password"
value={password}
<PasswordField
placeholder=""
onChange={(e) => {
setPassword(e.target.value);
}}
Expand Down
11 changes: 5 additions & 6 deletions frontend/src/pages/auth/SignUpForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Link, useNavigate } from 'react-router-dom';
import { FaCode } from 'react-icons/fa';
import AuthAPI from '../../api/users/auth';
import type { AxiosError } from 'axios';
import PasswordField from '../../components/content/PasswordField';

const SignUpForm: React.FC = () => {
const [username, setUsername] = useState('');
Expand Down Expand Up @@ -80,9 +81,8 @@ const SignUpForm: React.FC = () => {
</FormControl>
<FormControl isRequired>
<FormLabel>Password</FormLabel>
<Input
type="password"
value={password}
<PasswordField
placeholder=""
onChange={(e) => {
setPassword(e.target.value);
}}
Expand All @@ -92,9 +92,8 @@ const SignUpForm: React.FC = () => {

<FormControl isRequired>
<FormLabel>Confirm Password</FormLabel>
<Input
type="password"
value={confirmPassword}
<PasswordField
placeholder=""
onChange={(e) => {
setConfirmPassword(e.target.value);
}}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/collaboration/CollaborationRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ const CollaborationRoom: React.FC<CollaborationRoomProps> = ({ isMatchingRoom }:
return (
<Box>
<Flex mt={4} mx={4} justifyContent="space-between">
<RoomInfo />
<RoomInfo isMatchingRoom={isMatchingRoom} />
{isMatchingRoom && (
<Box>
<Button
Expand Down
32 changes: 1 addition & 31 deletions frontend/src/pages/collaboration/CreatePracticeRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,14 @@ import {
ModalCloseButton,
ModalBody,
Stack,
Accordion,
AccordionItem,
AccordionButton,
AccordionIcon,
AccordionPanel,
Button,
Box,
Divider,
HStack,
Input,
Text,
useToast,
} from '@chakra-ui/react';
import { BiDoorOpen } from 'react-icons/bi';
import PasswordField from '../../components/content/PasswordField';
import { v4 as uuidv4, validate, version } from 'uuid';
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
Expand All @@ -29,11 +22,10 @@ const CreatePracticeRoom: React.FC = () => {
const toast = useToast();
const navigate = useNavigate();
const [isPracticeRoomOpen, setIsPracticeRoomOpen] = useState(false);
const [roomPassword, setRoomPassword] = useState('');
const [roomIdInput, setRoomIdInput] = useState('');
const handleRoomCreation = (): void => {
const roomId = uuidv4();
navigate(`/practice/${roomId}`, { state: roomPassword });
navigate(`/practice/${roomId}`);
};
const handleRoomJoin = (): void => {
if (!validate(roomIdInput) || version(roomIdInput) !== 4) {
Expand Down Expand Up @@ -76,28 +68,6 @@ const CreatePracticeRoom: React.FC = () => {
<Stack marginBottom={4}>
<Text fontWeight="bold">Create a room</Text>
<Text>Create a new practice room and invite your friends to join to start coding together!</Text>
<Accordion allowToggle marginY={2}>
<AccordionItem>
<AccordionButton>
<Box as="span" flex={1} textAlign="left">
Optionally, set a password to protect the room
</Box>
<AccordionIcon />
</AccordionButton>
<AccordionPanel>
<Text marginBottom={4}>
Protect your practice room with a password. Users who want to join the room will be prompted for
the password before they can join.
</Text>
<PasswordField
placeholder="Room password (optional)"
onChange={(e) => {
setRoomPassword(e.target.value);
}}
/>
</AccordionPanel>
</AccordionItem>
</Accordion>
<Button onClick={handleRoomCreation}>Create room</Button>
</Stack>
<Divider />
Expand Down
Loading