Skip to content

Commit 780aa3d

Browse files
committed
Merge branch 'staging' into solomon/add-create-test
2 parents c236012 + bb8cc46 commit 780aa3d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1568
-291
lines changed

.github/workflows/test.yml

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
steps:
2020
- name: Checkout repository
2121
uses: actions/checkout@v2
22-
22+
2323
- name: Set up .env
2424
env:
2525
QUESTION_FIREBASE_CREDENTIAL_PATH: ${{ vars.QUESTION_SERVICE_FIREBASE_CREDENTIAL_PATH }}
@@ -30,7 +30,7 @@ jobs:
3030
echo "FIREBASE_CREDENTIAL_PATH=$QUESTION_FIREBASE_CREDENTIAL_PATH" >> .env
3131
echo "JWT_SECRET=$JWT_SECRET" >> .env
3232
echo "EXECUTION_SERVICE_URL=$EXECUTION_SERVICE_URL" >> .env
33-
33+
3434
- name: Set up credentials
3535
env:
3636
QUESTION_FIREBASE_JSON: ${{ secrets.QUESTION_SERVICE_FIREBASE_CREDENTIAL }}
@@ -41,8 +41,8 @@ jobs:
4141
4242
- name: Setup Go
4343
uses: actions/setup-go@v5
44-
with:
45-
go-version: "1.23.x"
44+
with:
45+
go-version: '1.23.x'
4646

4747
- name: Install Go dependencies
4848
run: |
@@ -51,7 +51,7 @@ jobs:
5151
5252
- name: Install firebase tools
5353
run: curl -sL firebase.tools | bash
54-
54+
5555
- name: Run Go tests with Firebase emulator
5656
run: firebase emulators:exec --only firestore 'cd ./apps/question-service; go test -v ./tests'
5757

@@ -66,11 +66,11 @@ jobs:
6666
run: |
6767
cd ./apps/frontend
6868
cp .env.example .env
69-
69+
7070
- name: Set up Node.js
7171
uses: actions/setup-node@v2
7272
with:
73-
node-version: "22"
73+
node-version: '22'
7474

7575
- name: Install pnpm
7676
run: npm i -g pnpm
@@ -83,7 +83,7 @@ jobs:
8383
- name: Run tests
8484
run: |
8585
cd ./apps/frontend
86-
pnpm test
86+
pnpm unit-test
8787
8888
test-docker-compose:
8989
runs-on: ubuntu-latest
@@ -148,13 +148,13 @@ jobs:
148148
cd ../history-service
149149
echo "FIREBASE_CREDENTIAL_PATH=$HISTORY_FIREBASE_CREDENTIAL_PATH" >> .env
150150
echo "PORT=$HISTORY_SERVICE_PORT" >> .env
151-
echo "RABBITMQ_URL=$RABBITMQ_URL" >> .env
152-
151+
echo "RABBMITMQ_URL=$RABBITMQ_URL" >> .env
152+
153153
cd ../execution-service
154154
echo "FIREBASE_CREDENTIAL_PATH=$EXECUTION_FIREBASE_CREDENTIAL_PATH" >> .env
155155
echo "PORT=$EXECUTION_SERVICE_PORT" >> .env
156156
echo "HISTORY_SERVICE_URL=$HISTORY_SERVICE_URL" >> .env
157-
echo "RABBITMQ_URL=$RABBITMQ_URL" >> .env
157+
echo "RABBMITMQ_URL=$RABBITMQ_URL" >> .env
158158
159159
cd ../signalling-service
160160
echo "PORT=$SIGNALLING_SERVICE_PORT" >> .env
@@ -173,7 +173,7 @@ jobs:
173173
174174
cd ../history-service
175175
echo "$HISTORY_FIREBASE_JSON" > "./$HISTORY_FIREBASE_CREDENTIAL_PATH"
176-
176+
177177
cd ../execution-service
178178
echo "$EXECUTION_FIREBASE_JSON" > "./$EXECUTION_FIREBASE_CREDENTIAL_PATH"
179179
@@ -201,6 +201,7 @@ jobs:
201201
SIGNALLING_SERVICE_URL: ${{ vars.SIGNALLING_SERVICE_URL }}
202202
EXECUTION_SERVICE_URL: ${{ vars.EXECUTION_SERVICE_URL }}
203203
run: |
204+
docker ps -a
204205
echo "Testing Question Service..."
205206
curl -sSL -o /dev/null $QUESTION_SERVICE_URL && echo "Question Service is up"
206207
echo "Testing User Service..."
@@ -225,3 +226,30 @@ jobs:
225226
echo "WebSocket for Signalling Service is live"
226227
fi
227228
# We can add more tests here
229+
230+
- name: Install pnpm
231+
uses: pnpm/action-setup@v4
232+
with:
233+
version: 9.1.4
234+
235+
- name: Install dependencies
236+
run: |
237+
cd ./apps/frontend
238+
pnpm i
239+
240+
- name: Install Chrome WebDriver
241+
uses: nanasess/setup-chromedriver@v2
242+
with:
243+
chromedriver-version: '130.0.6723.116'
244+
- name: Install Edge
245+
uses: browser-actions/setup-edge@v1
246+
with:
247+
edge-version: stable
248+
249+
- name: Install Geckodriver
250+
uses: browser-actions/setup-geckodriver@latest
251+
252+
- name: Run Browser Test
253+
run: |
254+
cd ./apps/frontend
255+
pnpm browser-test

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,38 @@
99
- You can choose to develop individual microservices within separate folders within this repository **OR** use individual repositories (all public) for each microservice.
1010
- In the latter scenario, you should enable sub-modules on this GitHub classroom repository to manage the development/deployment **AND** add your mentor to the individual repositories as a collaborator.
1111
- The teaching team should be given access to the repositories as we may require viewing the history of the repository in case of any disputes or disagreements.
12+
13+
---
14+
15+
## Architecture Diagram
16+
17+
![Overall Architecture Diagram](./docs/architecture_diagram.png)
18+
19+
The overall architecture of PeerPrep follows a microservices architecture. The client acts as an orchestrator for the interaction between the different services.
20+
21+
## Screenshots
22+
23+
![Home Page](./docs/home_page.png)
24+
25+
![Collaboration Page](./docs/collab_page_1.png)
26+
27+
![Collaboration Page](./docs/collab_page_2.png)
28+
29+
![Question Page](./docs/question_page.png)
30+
31+
![Question Page](./docs/indiv_question_page.png)
32+
33+
![History Page](./docs/submission_history_page.png)
34+
35+
## More details
36+
37+
- [Frontend](./apps/frontend/README.md)
38+
- [User Service](./apps/user-service/README.md)
39+
- [Question Service](./apps/question-service/README.md)
40+
- [Matching Service](./apps/matching-service/README.md)
41+
- [Signalling Service](./apps/signalling-service/README.md)
42+
- [History Service](./apps/history-service/README.md)
43+
- [Execution Service](./apps/execution-service/README.md)
44+
- [CI/CD Guide](./docs/cicid.md)
45+
- [Docker Compose Guide](./apps/README.md)
46+
- [Set Up Guide](./docs/setup.md)

apps/README.md

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
This project uses Docker Compose to manage multiple services such as a frontend, backend, and a database. The configuration is defined in the `docker-compose.yml` file, and environment variables can be stored in environment files for different environments (e.g., development, production).
44

5+
More details on how to set up Docker Compose can be found [here](../docs/setup.md)
6+
57
## Prerequisites
68

79
Before you begin, ensure you have the following installed on your machine:
@@ -30,11 +32,25 @@ In the `./apps` directory:
3032
├── user-service
3133
│ ├── Dockerfile # Dockerfile for user-service
3234
│ └── ... (other user-service files)
33-
35+
├── execution-service
36+
│ ├── Dockerfile # Dockerfile for execution-service
37+
│ └── ... (other execution-service files)
38+
├── signalling-service
39+
│ ├── Dockerfile # Dockerfile for signalling-service
40+
│ └── ... (other signalling-service files)
41+
├── history-service
42+
│ ├── Dockerfile # Dockerfile for history-service
43+
│ └── ... (other history-service files)
3444
```
3545

3646
## Docker Compose Setup
3747

48+
Ensure that you are currently using **Docker Compose v2** in your local Docker Desktop.
49+
- Launch your local Docker Desktop application
50+
- Click on settings button at the top right hand corner (beside the name)
51+
- Under the General tab, scroll down until you see a checkbox that says Use Docker Compose V2, ensure that the box is checked then apply and restart (refer to the image below)
52+
![Docker Compose V2](https://github.com/user-attachments/assets/3b8d47c2-c488-4fc1-804d-418ffebbdd9c)
53+
3854
By using multiple Dockerfiles in Docker Compose, we can manage complex multi-container applications where each service has its own environment and build process.
3955

4056
1. Build and Start the Application
@@ -54,11 +70,15 @@ This will:
5470

5571
Once running, you can access:
5672

57-
- The **frontend** at http://localhost:3000
58-
- The **user service** at http://localhost:3001
59-
- The **question service** at http://localhost:8080 (REST) and http://localhost:50051 (gRPC)
60-
- The **matching service** at http://localhost:8081
61-
- The **redis service** at http://localhost:6379
73+
- The [**frontend**](./frontend/README.md) at http://localhost:3000
74+
- The [**user-service**](./user-service/README.md) at http://localhost:3001
75+
- The [**question-service**](./question-service/README.md) at http://localhost:8080 (REST) and http://localhost:50051 (gRPC)
76+
- The [**matching-service**](./matching-service/README.md) at http://localhost:8081
77+
- The [**history-service**](./history-service/README.md) at http://localhost:8082
78+
- The [**execution-service**](./execution-service/README.md) at http://localhost:8083
79+
- The [**signalling-service**](./signalling-service/README.md) at http://localhost:4444
80+
- The **redis** at http://localhost:6379
81+
- The **rabbitmq** at http://localhost:5672
6282

6383
3. Stopping Services
6484

@@ -76,6 +96,11 @@ This command will stop and remove the containers, networks, and volumes created
7696

7797
- **Port Conflicts**: If you encounter port conflicts, ensure the host ports specified in docker-compose.yml (e.g., 3000:3000) are not in use by other applications.
7898
- **Environment Variables Not Loaded**: Ensure the `.env` files are in the correct directories as found in the `docker-compose.yml` file.
99+
- **Command execution failed**: When you try running test cases or submitting the code in the collaborative environment, if you encounter the following error message:
100+
```bash
101+
Command execution failed: Unable to find image 'apps-python-sandbox:latest' locally docker: Error response from daemon: pull access denied for apps-python-sandbox, repository does not exist or may require 'docker login': denied: requested access to the resource is denied. See 'docker run --help'. : exit status 125
102+
```
103+
Ensure that you have **Docker Compose V2** enabled for your Docker Desktop application. Please refer to the Docker Compose setup guide above to enable it locally.
79104

80105
### Known Issues
81106

apps/docker-compose.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@ services:
129129
- apps_network
130130
container_name: python-sandbox
131131

132+
node-sandbox:
133+
build:
134+
context: ./execution-service/execution/node
135+
dockerfile: Dockerfile
136+
networks:
137+
- apps_network
138+
container_name: node-sandbox
139+
stdin_open: true # Enables interactive mode for passing standard input
140+
132141
networks:
133142
apps_network:
134143

apps/execution-service/README.md

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,59 @@
11
# Execution Service
22

3+
The Execution Service provides backend functionality for running and validating code executions or submissions within a coding platform. It enables users to execute code against test cases and receive feedback on the correctness of their solutions.
4+
5+
The Execution Service incorporates a code execution mechanism designed to run user-submitted solutions within an isolated, sandboxed environment. This approach enhances security by preventing arbitrary code from interacting with the host system directly and allows for performance monitoring
6+
7+
### Technology Stack
8+
9+
- Golang (Go): Statically typed, compiled language with low latency. Fast and efficient processing is ideal for high-read, high-write environments like in Execution Service, when many users run tests or submit tests.
10+
- Rest Server: chi router was utilized which supports CORS, logging and timeout via middlewares. It is stateless, which reduces coupling and enhances scalability and reliability, simplicity and flexibility. For example, clients may make requests to different server instances when scaled.
11+
- Firebase Firestore: NoSQL Document database that is designed for automatic horizontal scaling and schema-less design that allows for flexibility as number of tests increases or more users run tests.
12+
- Docker: used to containerize the Execution Service to simplify deployment. Additionally used to provide a sandboxed execution environment for user-submitted code, ensuring security by limiting code access to the host system and managing dependencies independently.
13+
14+
### Execution Process
15+
16+
For execution of user code (running of test cases without submission), only visible (public) and custom test cases are executed.
17+
18+
![Diagram of code execution process](../../docs/exeuction_process.png)
19+
20+
### Submission Process
21+
22+
For submission of user code, both visible (public) and hidden testcases are executed, before calling the history-service API to submit the submission data, code and test results.
23+
24+
![Diagram of code submission process](../../docs/submission_process.png)
25+
26+
### Design Decisions
27+
28+
1. **Docker Containerisation**
29+
a. Upon receiving a code execution request, the service dynamically creates a Docker container with a controlled environment tailored to Python
30+
b. The Docker container is set up with only the minimal permissions and resources needed to execute the code, restricting the execution environment to reduce risk
31+
c. This containerized environment is automatically destroyed after execution, ensuring no residual data or state remains between executions
32+
33+
2. **Security and Isolation**
34+
a. Containers provide isolation from the host system, limiting any interaction between user code and the underlying infrastructure
35+
b. Only essential files and libraries required for code execution are included, reducing potential attack surfaces within each container. The sandboxed, container-based execution system provides a secure and efficient way to run user code submissions.
36+
37+
The sandboxed, container-based execution system provides a secure and efficient way to run user code submissions.
38+
39+
### Communication between Execution and History Service
40+
41+
The communication between the Execution service and the History service is implemented through a RabbitMQ Message Queue. RabbitMQ is ideal for message queues in microservices due to its reliability, flexible routing, and scalability. It ensures messages aren’t lost through durable queues and supports complex routing to handle diverse messaging needs.
42+
43+
Asynchronous communication was chosen as a user’s submission history did not need to be updated immediately. Instead of waiting for a response, the Execution Service can put the message in a queue and continue processing other requests.
44+
45+
![RabbitMQ Message Queue](./../../docs/rabbit_mq_queue.png)
46+
47+
A message queue allows services to communicate without depending on each other's availability. The Execution Service can send a message to the queue, and the History Service can process it when it’s ready. This decoupling promotes loose coupling and reduces dependencies between services, which helps maintain a robust and adaptable system.
48+
49+
---
50+
51+
## Setup
52+
53+
### Prerequisites
54+
55+
Ensure you have Go installed on your machine.
56+
357
### Installation
458

559
1. Install dependencies:
@@ -61,10 +115,10 @@ The server will be available at http://localhost:8083.
61115

62116
## API Endpoints
63117

64-
- `POST /tests/populate`
65-
- `GET /tests/{questionDocRefId}/`
66-
- `POST /tests/{questionDocRefId}/execute`
67-
- `POST /tests/{questionDocRefId}/submit`
118+
- `POST: /tests/populate`: Deletes and repopulates all tests in Firebase
119+
- `GET: /{questionDocRefId}`: Reads the public testcases for the question, identified by the question reference ID
120+
- `POST: /{questionDocRefId}/execute`: Executes the public testcases for the question, identified by the question reference ID
121+
- `POST: /{questionDocRefId}/submit`: Executes the public and hidden testcases for the question, identified by the question reference ID, and submits the code submission to History Service
68122

69123
## Managing Firebase
70124

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package constants
22

33
const (
4-
JAVA = "Java"
5-
PYTHON = "Python"
6-
GOLANG = "Golang"
7-
JAVASCRIPT = "Javascript"
8-
CPP = "C++"
4+
JAVA = "java"
5+
PYTHON = "python"
6+
GOLANG = "golang"
7+
JAVASCRIPT = "javascript"
8+
CPP = "c++"
99
)
1010

1111
const (
@@ -17,6 +17,6 @@ var IS_VALID_LANGUAGE = map[string]bool{
1717
PYTHON: true,
1818
//JAVA: true,
1919
//GOLANG: true,
20-
//JAVASCRIPT: true,
20+
JAVASCRIPT: true,
2121
//CPP: true,
2222
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Use a slim Node.js image
2+
FROM node:18-slim
3+
4+
# Set the working directory
5+
WORKDIR /app
6+
7+
# Install any dependencies if necessary (you can skip if no dependencies)
8+
# COPY package*.json ./
9+
# RUN npm install
10+
11+
# No entry point or CMD needed as you'll provide the command at runtime
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package node
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os/exec"
7+
"strings"
8+
)
9+
10+
func RunJavaScriptCode(code string, input string) (string, string, error) {
11+
cmd := exec.Command(
12+
"docker", "run", "--rm",
13+
"-i", // allows standard input to be passed in
14+
"apps-node-sandbox", // Docker image with Node.js environment
15+
"node", "-e", code, // Runs JavaScript code with Node.js
16+
)
17+
18+
// Pass input to the JavaScript script
19+
cmd.Stdin = bytes.NewBufferString(input)
20+
21+
// Capture standard output and error output
22+
var output bytes.Buffer
23+
var errorOutput bytes.Buffer
24+
cmd.Stdout = &output
25+
cmd.Stderr = &errorOutput
26+
27+
// Run the command
28+
if err := cmd.Run(); err != nil {
29+
return "", fmt.Sprintf("Command execution failed: %s: %v", errorOutput.String(), err), nil
30+
}
31+
32+
return strings.TrimSuffix(output.String(), "\n"), strings.TrimSuffix(errorOutput.String(), "\n"), nil
33+
}

0 commit comments

Comments
 (0)