diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0d2a1ad306..f37cd6c7a4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,9 +10,82 @@ on: branches: - main - staging + workflow_dispatch: jobs: - test: + question-service-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up .env + env: + QUESTION_FIREBASE_CREDENTIAL_PATH: ${{ vars.QUESTION_SERVICE_FIREBASE_CREDENTIAL_PATH }} + JWT_SECRET: ${{ secrets.JWT_SECRET }} + EXECUTION_SERVICE_URL: ${{ vars.EXECUTION_SERVICE_URL }} + run: | + cd ./apps/question-service + echo "FIREBASE_CREDENTIAL_PATH=$QUESTION_FIREBASE_CREDENTIAL_PATH" >> .env + echo "JWT_SECRET=$JWT_SECRET" >> .env + echo "EXECUTION_SERVICE_URL=$EXECUTION_SERVICE_URL" >> .env + + - name: Set up credentials + env: + QUESTION_FIREBASE_JSON: ${{ secrets.QUESTION_SERVICE_FIREBASE_CREDENTIAL }} + QUESTION_FIREBASE_CREDENTIAL_PATH: ${{ vars.QUESTION_SERVICE_FIREBASE_CREDENTIAL_PATH }} + run: | + cd ./apps/question-service + echo "$QUESTION_FIREBASE_JSON" > "./$QUESTION_FIREBASE_CREDENTIAL_PATH" + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.23.x' + + - name: Install Go dependencies + run: | + cd ./apps/question-service + go mod tidy + + - name: Install firebase tools + run: curl -sL firebase.tools | bash + + - name: Run Go tests with Firebase emulator + run: firebase emulators:exec --only firestore 'cd ./apps/question-service; go test -v ./tests' + + frontend-unit-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Setup .env + run: | + cd ./apps/frontend + cp .env.example .env + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '22' + + - name: Install pnpm + run: npm i -g pnpm + + - name: Install dependencies + run: | + cd ./apps/frontend + pnpm i + + - name: Run tests + run: | + cd ./apps/frontend + pnpm unit-test + + test-docker-compose: runs-on: ubuntu-latest steps: @@ -29,22 +102,36 @@ jobs: QUESTION_SERVICE_URL: ${{ vars.QUESTION_SERVICE_URL }} USER_SERVICE_URL: ${{ vars.USER_SERVICE_URL }} MATCHING_SERVICE_URL: ${{ vars.MATCHING_SERVICE_URL }} + HISTORY_SERVICE_URL: ${{ vars.HISTORY_SERVICE_URL }} + SIGNALLING_SERVICE_URL: ${{ vars.SIGNALLING_SERVICE_URL }} + EXECUTION_SERVICE_URL: ${{ vars.EXECUTION_SERVICE_URL }} JWT_SECRET: ${{ secrets.JWT_SECRET }} - FIREBASE_CREDENTIAL_PATH: ${{ vars.QUESTION_SERVICE_FIREBASE_CREDENTIAL_PATH }} + QUESTION_FIREBASE_CREDENTIAL_PATH: ${{ vars.QUESTION_SERVICE_FIREBASE_CREDENTIAL_PATH }} + HISTORY_FIREBASE_CREDENTIAL_PATH: ${{ vars.HISTORY_SERVICE_FIREBASE_CREDENTIAL_PATH }} + EXECUTION_FIREBASE_CREDENTIAL_PATH: ${{ vars.EXECUTION_SERVICE_FIREBASE_CREDENTIAL_PATH }} DB_CLOUD_URI: ${{ secrets.USER_SERVICE_DB_CLOUD_URI }} USER_SERVICE_PORT: ${{ vars.USER_SERVICE_PORT }} MATCHING_SERVICE_PORT: ${{ vars.MATCHING_SERVICE_PORT }} + HISTORY_SERVICE_PORT: ${{ vars.HISTORY_SERVICE_PORT }} + SIGNALLING_SERVICE_PORT: ${{ vars.SIGNALLING_SERVICE_PORT }} + EXECUTION_SERVICE_PORT: ${{ vars.EXECUTION_SERVICE_PORT }} MATCHING_SERVICE_TIMEOUT: ${{ vars.MATCHING_SERVICE_TIMEOUT }} REDIS_URL: ${{ vars.REDIS_URL }} + RABBITMQ_URL: ${{ vars.RABBITMQ_URL }} + QUESTION_SERVICE_GRPC_URL: ${{ vars.QUESTION_SERVICE_GPRC_URL }} run: | cd ./apps/frontend echo "NEXT_PUBLIC_QUESTION_SERVICE_URL=$QUESTION_SERVICE_URL" >> .env echo "NEXT_PUBLIC_USER_SERVICE_URL=$USER_SERVICE_URL" >> .env echo "NEXT_PUBLIC_MATCHING_SERVICE_URL=$MATCHING_SERVICE_URL" >> .env + echo "NEXT_PUBLIC_HISTORY_SERVICE_URL=$HISTORY_SERVICE_URL" >> .env + echo "NEXT_PUBLIC_SIGNALLING_SERVICE_URL=$SIGNALLING_SERVICE_URL" >> .env + echo "NEXT_PUBLIC_EXECUTION_SERVICE_URL=EXECUTION_SERVICE_URL" >> .env cd ../question-service - echo "FIREBASE_CREDENTIAL_PATH=$FIREBASE_CREDENTIAL_PATH" >> .env + echo "FIREBASE_CREDENTIAL_PATH=$QUESTION_FIREBASE_CREDENTIAL_PATH" >> .env echo "JWT_SECRET=$JWT_SECRET" >> .env + echo "EXECUTION_SERVICE_URL=$EXECUTION_SERVICE_URL" >> .env cd ../user-service echo "DB_CLOUD_URI=$DB_CLOUD_URI" >> .env @@ -56,14 +143,39 @@ jobs: echo "MATCH_TIMEOUT=$MATCHING_SERVICE_TIMEOUT" >> .env echo "JWT_SECRET=$JWT_SECRET" >> .env echo "REDIS_URL=$REDIS_URL" >> .env + echo "QUESTION_SERVICE_GRPC_URL=$QUESTION_SERVICE_GRPC_URL" >> .env + + cd ../history-service + echo "FIREBASE_CREDENTIAL_PATH=$HISTORY_FIREBASE_CREDENTIAL_PATH" >> .env + echo "PORT=$HISTORY_SERVICE_PORT" >> .env + echo "RABBMITMQ_URL=$RABBITMQ_URL" >> .env + + cd ../execution-service + echo "FIREBASE_CREDENTIAL_PATH=$EXECUTION_FIREBASE_CREDENTIAL_PATH" >> .env + echo "PORT=$EXECUTION_SERVICE_PORT" >> .env + echo "HISTORY_SERVICE_URL=$HISTORY_SERVICE_URL" >> .env + echo "RABBMITMQ_URL=$RABBITMQ_URL" >> .env + + cd ../signalling-service + echo "PORT=$SIGNALLING_SERVICE_PORT" >> .env - name: Create Database Credential Files env: - FIREBASE_JSON: ${{ secrets.QUESTION_SERVICE_FIREBASE_CREDENTIAL }} - FIREBASE_CREDENTIAL_PATH: ${{ vars.QUESTION_SERVICE_FIREBASE_CREDENTIAL_PATH }} + QUESTION_FIREBASE_JSON: ${{ secrets.QUESTION_SERVICE_FIREBASE_CREDENTIAL }} + QUESTION_FIREBASE_CREDENTIAL_PATH: ${{ vars.QUESTION_SERVICE_FIREBASE_CREDENTIAL_PATH }} + HISTORY_FIREBASE_JSON: ${{ secrets.HISTORY_SERVICE_FIREBASE_CREDENTIAL }} + HISTORY_FIREBASE_CREDENTIAL_PATH: ${{ vars.HISTORY_SERVICE_FIREBASE_CREDENTIAL_PATH }} + EXECUTION_FIREBASE_JSON: ${{ secrets.EXECUTION_SERVICE_FIREBASE_CREDENTIAL }} + EXECUTION_FIREBASE_CREDENTIAL_PATH: ${{ vars.EXECUTION_SERVICE_FIREBASE_CREDENTIAL_PATH }} run: | cd ./apps/question-service - echo "$FIREBASE_JSON" > "./$FIREBASE_CREDENTIAL_PATH" + echo "$QUESTION_FIREBASE_JSON" > "./$QUESTION_FIREBASE_CREDENTIAL_PATH" + + cd ../history-service + echo "$HISTORY_FIREBASE_JSON" > "./$HISTORY_FIREBASE_CREDENTIAL_PATH" + + cd ../execution-service + echo "$EXECUTION_FIREBASE_JSON" > "./$EXECUTION_FIREBASE_CREDENTIAL_PATH" - name: Build and Run Services run: | @@ -85,13 +197,21 @@ jobs: USER_SERVICE_URL: ${{ vars.USER_SERVICE_URL }} QUESTION_SERVICE_URL: ${{ vars.QUESTION_SERVICE_URL }} MATCHING_SERVICE_URL: ${{ vars.MATCHING_SERVICE_URL }} + HISTORY_SERVICE_URL: ${{ vars.HISTORY_SERVICE_URL }} + SIGNALLING_SERVICE_URL: ${{ vars.SIGNALLING_SERVICE_URL }} + EXECUTION_SERVICE_URL: ${{ vars.EXECUTION_SERVICE_URL }} run: | + docker ps -a echo "Testing Question Service..." curl -sSL -o /dev/null $QUESTION_SERVICE_URL && echo "Question Service is up" echo "Testing User Service..." curl -fsSL -o /dev/null $USER_SERVICE_URL && echo "User Service is up" echo "Testing Frontend..." curl -fsSL -o /dev/null $FRONTEND_URL && echo "Frontend is up" + echo "Testing History Service..." + curl -fsSL -o /dev/null $HISTORY_SERVICE_URL && echo "History Service is up" + echo "Testing Execution Service..." + curl -fsSL -o /dev/null $EXECUTION_SERVICE_URL && echo "Execution Service is up" echo "Testing Matching Service..." if ! (echo "Hello" | websocat $MATCHING_SERVICE_URL); then echo "WebSocket for Matching Service is not live" @@ -99,5 +219,37 @@ jobs: echo "WebSocket for Matching Service is live" fi # Add in test for matching service in the future - + echo "Testing Signalling Service..." + if ! (echo "Hello" | websocat $SIGNALLING_SERVICE_URL); then + echo "WebSocket for Signalling Service is not live" + else + echo "WebSocket for Signalling Service is live" + fi # We can add more tests here + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.1.4 + + - name: Install dependencies + run: | + cd ./apps/frontend + pnpm i + + - name: Install Chrome WebDriver + uses: nanasess/setup-chromedriver@v2 + with: + chromedriver-version: '130.0.6723.116' + - name: Install Edge + uses: browser-actions/setup-edge@v1 + with: + edge-version: stable + + - name: Install Geckodriver + uses: browser-actions/setup-geckodriver@latest + + - name: Run Browser Test + run: | + cd ./apps/frontend + pnpm browser-test diff --git a/README.md b/README.md index bdb87090be..68984bc014 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,38 @@ - You can choose to develop individual microservices within separate folders within this repository **OR** use individual repositories (all public) for each microservice. - 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. - 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. + +--- + +## Architecture Diagram + +![Overall Architecture Diagram](./docs/architecture_diagram.png) + +The overall architecture of PeerPrep follows a microservices architecture. The client acts as an orchestrator for the interaction between the different services. + +## Screenshots + +![Home Page](./docs/home_page.png) + +![Collaboration Page](./docs/collab_page_1.png) + +![Collaboration Page](./docs/collab_page_2.png) + +![Question Page](./docs/question_page.png) + +![Question Page](./docs/indiv_question_page.png) + +![History Page](./docs/submission_history_page.png) + +## More details + +- [Frontend](./apps/frontend/README.md) +- [User Service](./apps/user-service/README.md) +- [Question Service](./apps/question-service/README.md) +- [Matching Service](./apps/matching-service/README.md) +- [Signalling Service](./apps/signalling-service/README.md) +- [History Service](./apps/history-service/README.md) +- [Execution Service](./apps/execution-service/README.md) +- [CI/CD Guide](./docs/cicid.md) +- [Docker Compose Guide](./apps/README.md) +- [Set Up Guide](./docs/setup.md) diff --git a/apps/README.md b/apps/README.md index 18525dd4f6..696619fb27 100644 --- a/apps/README.md +++ b/apps/README.md @@ -2,6 +2,8 @@ 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). +More details on how to set up Docker Compose can be found [here](../docs/setup.md) + ## Prerequisites Before you begin, ensure you have the following installed on your machine: @@ -30,11 +32,25 @@ In the `./apps` directory: ├── user-service │ ├── Dockerfile # Dockerfile for user-service │ └── ... (other user-service files) - +├── execution-service +│ ├── Dockerfile # Dockerfile for execution-service +│ └── ... (other execution-service files) +├── signalling-service +│ ├── Dockerfile # Dockerfile for signalling-service +│ └── ... (other signalling-service files) +├── history-service +│ ├── Dockerfile # Dockerfile for history-service +│ └── ... (other history-service files) ``` ## Docker Compose Setup +Ensure that you are currently using **Docker Compose v2** in your local Docker Desktop. +- Launch your local Docker Desktop application +- Click on settings button at the top right hand corner (beside the name) +- 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) +![Docker Compose V2](https://github.com/user-attachments/assets/3b8d47c2-c488-4fc1-804d-418ffebbdd9c) + By using multiple Dockerfiles in Docker Compose, we can manage complex multi-container applications where each service has its own environment and build process. 1. Build and Start the Application @@ -54,11 +70,15 @@ This will: Once running, you can access: -- The **frontend** at http://localhost:3000 -- The **user service** at http://localhost:3001 -- The **question service** at http://localhost:8080 -- The **matching service** at http://localhost:8081 -- The **redis service** at http://localhost:6379 +- The [**frontend**](./frontend/README.md) at http://localhost:3000 +- The [**user-service**](./user-service/README.md) at http://localhost:3001 +- The [**question-service**](./question-service/README.md) at http://localhost:8080 (REST) and http://localhost:50051 (gRPC) +- The [**matching-service**](./matching-service/README.md) at http://localhost:8081 +- The [**history-service**](./history-service/README.md) at http://localhost:8082 +- The [**execution-service**](./execution-service/README.md) at http://localhost:8083 +- The [**signalling-service**](./signalling-service/README.md) at http://localhost:4444 +- The **redis** at http://localhost:6379 +- The **rabbitmq** at http://localhost:5672 3. Stopping Services @@ -76,6 +96,11 @@ This command will stop and remove the containers, networks, and volumes created - **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. - **Environment Variables Not Loaded**: Ensure the `.env` files are in the correct directories as found in the `docker-compose.yml` file. +- **Command execution failed**: When you try running test cases or submitting the code in the collaborative environment, if you encounter the following error message: + ```bash + 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 + ``` + 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. ### Known Issues diff --git a/apps/docker-compose.yml b/apps/docker-compose.yml index e69e2bcc65..77399d8bbc 100644 --- a/apps/docker-compose.yml +++ b/apps/docker-compose.yml @@ -11,6 +11,8 @@ services: - ./frontend/.env volumes: - ./frontend:/frontend + depends_on: + - signalling-service user-service: build: @@ -31,6 +33,7 @@ services: dockerfile: Dockerfile ports: - 8080:8080 + - 50051:50051 env_file: - ./question-service/.env networks: @@ -53,6 +56,50 @@ services: depends_on: - redis + history-service: + build: + context: ./history-service + dockerfile: Dockerfile + ports: + - 8082:8082 + env_file: + - ./history-service/.env + networks: + - apps_network + volumes: + - ./history-service:/history-service + depends_on: + - rabbitmq + + signalling-service: + build: + context: ./signalling-service + dockerfile: Dockerfile + ports: + - 4444:4444 + env_file: + - ./signalling-service/.env + networks: + - apps_network + volumes: + - ./signalling-service:/signalling-service + + execution-service: + build: + context: ./execution-service + dockerfile: Dockerfile + ports: + - 8083:8083 + env_file: + - ./execution-service/.env + networks: + - apps_network + volumes: + - ./execution-service:/execution-service + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + - rabbitmq + redis: image: redis:latest networks: @@ -61,5 +108,40 @@ services: - 6379:6379 container_name: redis-container + rabbitmq: + image: rabbitmq:3-management + networks: + - apps_network + ports: + - 5672:5672 # Port for RabbitMQ message broker + - 15672:15672 # Port for RabbitMQ Management UI + environment: + RABBITMQ_DEFAULT_USER: guest + RABBITMQ_DEFAULT_PASS: guest + volumes: + - rabbitmq_data:/var/lib/rabbitmq + + python-sandbox: + build: + context: ./execution-service/execution/python + dockerfile: Dockerfile + networks: + - apps_network + container_name: python-sandbox + + node-sandbox: + build: + context: ./execution-service/execution/node + dockerfile: Dockerfile + networks: + - apps_network + container_name: node-sandbox + stdin_open: true # Enables interactive mode for passing standard input + networks: apps_network: + +volumes: + # Mounts a volume for RabbitMQ data persistence. + # This ensures that data is not lost when the container is restarted or removed. + rabbitmq_data: diff --git a/apps/execution-service/.dockerignore b/apps/execution-service/.dockerignore new file mode 100644 index 0000000000..c6228e8d6c --- /dev/null +++ b/apps/execution-service/.dockerignore @@ -0,0 +1,9 @@ +.env.example + +.git +.gitignore + +.dockerignore +Dockerfile + +README.md diff --git a/apps/execution-service/.env.example b/apps/execution-service/.env.example new file mode 100644 index 0000000000..4122078fbd --- /dev/null +++ b/apps/execution-service/.env.example @@ -0,0 +1,10 @@ +FIREBASE_CREDENTIAL_PATH=cs3219-staging-codeexecution-firebase-adminsdk-ce48j-00ab09514c.json +PORT=8083 + +# If you are NOT USING docker, use the below variables +# HISTORY_SERVICE_URL=http://localhost:8082/ +# RABBITMQ_URL=amqp://guest:guest@localhost:5672/ + +# If you are USING docker, use the below variables +HISTORY_SERVICE_URL=http://history-service:8082/ +RABBITMQ_URL=amqp://guest:guest@rabbitmq:5672/ diff --git a/apps/execution-service/.gitignore b/apps/execution-service/.gitignore new file mode 100644 index 0000000000..ef986d267e --- /dev/null +++ b/apps/execution-service/.gitignore @@ -0,0 +1,2 @@ +.env +cs3219-staging-codeexecution-firebase-adminsdk-ce48j-00ab09514c.json diff --git a/apps/execution-service/Dockerfile b/apps/execution-service/Dockerfile new file mode 100644 index 0000000000..eda8bea8d0 --- /dev/null +++ b/apps/execution-service/Dockerfile @@ -0,0 +1,22 @@ +FROM golang:1.23 + +WORKDIR /usr/src/app + +# Install Docker CLI +RUN apt-get update && apt-get install -y \ + docker.io \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# pre-copy/cache go.mod for pre-downloading dependencies and only redownloading them in subsequent builds if they change +COPY go.mod go.sum ./ + +RUN go mod tidy && go mod download && go mod verify + +COPY . . + +RUN go build -v -o /usr/local/bin/app ./main.go + +EXPOSE 8083 + +CMD ["app"] diff --git a/apps/execution-service/README.md b/apps/execution-service/README.md new file mode 100644 index 0000000000..b1629b0b08 --- /dev/null +++ b/apps/execution-service/README.md @@ -0,0 +1,333 @@ +# Execution Service + +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. + +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 + +### Technology Stack + +- 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. +- 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. +- 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. +- 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. + +### Execution Process + +For execution of user code (running of test cases without submission), only visible (public) and custom test cases are executed. + +![Diagram of code execution process](../../docs/exeuction_process.png) + +### Submission Process + +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. + +![Diagram of code submission process](../../docs/submission_process.png) + +### Design Decisions + +1. **Docker Containerisation** + a. Upon receiving a code execution request, the service dynamically creates a Docker container with a controlled environment tailored to Python + 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 + c. This containerized environment is automatically destroyed after execution, ensuring no residual data or state remains between executions + +2. **Security and Isolation** + a. Containers provide isolation from the host system, limiting any interaction between user code and the underlying infrastructure + 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. + +The sandboxed, container-based execution system provides a secure and efficient way to run user code submissions. + +### Communication between Execution and History Service + +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. + +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. + +![RabbitMQ Message Queue](./../../docs/rabbit_mq_queue.png) + +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. + +--- + +## Setup + +### Prerequisites + +Ensure you have Go installed on your machine. + +### Installation + +1. Install dependencies: + +```bash +go mod tidy +``` + +2. Create the `.env` file from copying the `.env.example`, and copy the firebase JSON file into execution-service/ fill in the `FIREBASE_CREDENTIAL_PATH` with the path of the firebase credential JSON file. + +### Running the Application + +To start the server, run the following command: + +```bash +go run main.go +``` + +The server will be available at http://localhost:8083. + +### Setting up message queue with RabbitMQ + +A message queue is used to pass submission results asynchronously from the execution-service to the history-service. + +1. In order to do so, we can run the following command to set up a docker container for RabbitMQ: + +```bash +docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management +``` + +2. Then we can run the execution-service: + +```bash +go run main.go +``` + +3. We can run the history-service by changing our directory and running the same command: + +```bash +cd ../history-service +go run main.go +``` + +To view more details on the RabbitMQ queue, we can go to `localhost:15672`, and login using `guest` as the username and password. + +### Running the Application via Docker + +To run the application via Docker, run the following command: + +```bash +docker build -t execution-service . +``` + +```bash +docker run -p 8083:8083 --env-file .env -d execution-service +``` + +The server will be available at http://localhost:8083. + +## API Endpoints + +- `POST: /tests/populate`: Deletes and repopulates all tests in Firebase +- `GET: /{questionDocRefId}`: Reads the public testcases for the question, identified by the question reference ID +- `POST: /{questionDocRefId}/execute`: Executes the public testcases for the question, identified by the question reference ID +- `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 + +## Managing Firebase + +To reset and repopulate the database, run the following command: + +```bash +go run main.go +``` + +## Repopulate test cases + +To repopulate test cases, you need to repopulate the questions in the question-service, which will automatically call the execution-service to populate the test cases. + +In question-service, run the following command: + +```bash +go run main.go -populate +``` + +## API Documentation + +`GET /tests/{questionDocRefId}/` + +To read visible test cases via a question ID, run the following command: + +```bash +curl -X GET http://localhost:8083/tests/{questioinDocRefId}/ \ +-H "Content-Type: application/json" +``` + +The following json format will be returned: + +```json +[ + { + "input": "hello", + "expected": "olleh" + } +] +``` + +`POST /tests` + +To create a new test case, run the following command: + +```bash +curl -X POST http://localhost:8083/tests \ +-H "Content-Type: application/json" \ +-d '{ +"questionDocRefId": "sampleDocRefId123", +"questionTitle": "Sample Question Title", +"visibleTestCases": "2\nhello\nolleh\nHannah\nhannaH", +"hiddenTestCases": "2\nHannah\nhannaH\nabcdefg\ngfedcba" +}' +``` + +`PUT /tests/{questionDocRefId}` + +To update an existing test case from an existing question, run the following command: + +```bash +curl -X PUT http://localhost:8083/tests/{questionDocRefId} \ +-H "Content-Type: application/json" \ +-d '{ +"visibleTestCases": "2\nhello\nolleh\nHannah\nhannaH", +"hiddenTestCases": "2\nHannah\nhannaH\nabcdefg\ngfedcba" +}' +``` + +`DELETE /tests/{questionDocRefId}` + +To delete an existing test case from an existing question, run the following command: + +```bash +curl -X DELETE http://localhost:8083/tests/{questionDocRefId} \ +-H "Content-Type: application/json" +``` + +`POST /tests/{questionDocRefId}/execute` + +To execute test cases via a question ID without custom test cases, run the following command, with custom code and language: + +```bash +curl -X POST http://localhost:8083/tests/{questioinDocRefId}/execute \ +-H "Content-Type: application/json" \ +-d '{ +"code": "name = input()\nprint(name[::-1])", +"language": "Python" +}' +``` + +The following json format will be returned: + +```json +{ + "visibleTestResults": [ + { + "input": "hello", + "expected": "olleh", + "actual": "olleh", + "passed": true, + "error": "" + } + ], + "customTestResults": null +} +``` + +To execute visible and custom test cases via a question ID with custom test cases, run the following command, with custom code, language and custom test cases: + +```bash +curl -X POST http://localhost:8083/tests/{questioinDocRefId}/execute \ +-H "Content-Type: application/json" \ +-d '{ +"code": "name = input()\nprint(name[::-1])", +"language": "Python", +"customTestCases": "2\nHannah\nhannaH\nabcdefg\ngfedcba\n" +}' +``` + +The following json format will be returned: + +```json +{ + "visibleTestResults": [ + { + "input": "hello", + "expected": "olleh", + "actual": "olleh", + "passed": true, + "error": "" + } + ], + "customTestResults": [ + { + "input": "Hannah", + "expected": "hannaH", + "actual": "hannaH", + "passed": true, + "error": "" + }, + { + "input": "abcdefg", + "expected": "gfedcba", + "actual": "gfedcba", + "passed": true, + "error": "" + } + ] +} +``` + +`POST /tests/{questionDocRefId}/submit` + +To submit a solution and execute visible and hidden test cases via a question ID, run the following command, with custom code and language: + +```bash +curl -X POST http://localhost:8083/tests/{questioinDocRefId}/submit \ +-H "Content-Type: application/json" \ +-d '{ +"title": "Example Title", +"code": "name = input()\nprint(name[::-1])", +"language": "Python", +"user": "user123", +"matchedUser": "user456", +"matchedTopics": ["topic1", "topic2"], +"questionDifficulty": "Medium", +"questionTopics": ["Loops", "Strings"] +}' +``` + +The following json format will be returned: + +```json +{ + "visibleTestResults": [ + { + "input": "hello", + "expected": "olleh", + "actual": "olleh", + "passed": true, + "error": "" + } + ], + "hiddenTestResults": { + "passed": 2, + "total": 2 + }, + "status": "Accepted" +} +``` + +If compilation error exists or any of the tests (visible and hidden) fails, status "Attempted" will be returned: + +```json +{ + "visibleTestResults": [ + { + "input": "hello", + "expected": "olleh", + "actual": "", + "passed": false, + "error": "Command execution failed: Traceback (most recent call last):\n File \"/tmp/4149249165.py\", line 2, in \u003cmodule\u003e\n prit(name[::-1])\n ^^^^\nNameError: name 'prit' is not defined. Did you mean: 'print'?\n: %!w(*exec.ExitError=\u0026{0x4000364678 []})" + } + ], + "hiddenTestResults": { + "passed": 0, + "total": 2 + }, + "status": "Attempted" +} +``` diff --git a/apps/execution-service/constants/constant.go b/apps/execution-service/constants/constant.go new file mode 100644 index 0000000000..1e3810a3e4 --- /dev/null +++ b/apps/execution-service/constants/constant.go @@ -0,0 +1,22 @@ +package constants + +const ( + JAVA = "java" + PYTHON = "python" + GOLANG = "golang" + JAVASCRIPT = "javascript" + CPP = "c++" +) + +const ( + ACCEPTED = "Accepted" + ATTEMPTED = "Attempted" +) + +var IS_VALID_LANGUAGE = map[string]bool{ + PYTHON: true, + //JAVA: true, + //GOLANG: true, + JAVASCRIPT: true, + //CPP: true, +} diff --git a/apps/execution-service/execution/node/Dockerfile b/apps/execution-service/execution/node/Dockerfile new file mode 100644 index 0000000000..59ef6fcdee --- /dev/null +++ b/apps/execution-service/execution/node/Dockerfile @@ -0,0 +1,11 @@ +# Use a slim Node.js image +FROM node:18-slim + +# Set the working directory +WORKDIR /app + +# Install any dependencies if necessary (you can skip if no dependencies) +# COPY package*.json ./ +# RUN npm install + +# No entry point or CMD needed as you'll provide the command at runtime diff --git a/apps/execution-service/execution/node/javascript.go b/apps/execution-service/execution/node/javascript.go new file mode 100644 index 0000000000..c41c73c068 --- /dev/null +++ b/apps/execution-service/execution/node/javascript.go @@ -0,0 +1,33 @@ +package node + +import ( + "bytes" + "fmt" + "os/exec" + "strings" +) + +func RunJavaScriptCode(code string, input string) (string, string, error) { + cmd := exec.Command( + "docker", "run", "--rm", + "-i", // allows standard input to be passed in + "apps-node-sandbox", // Docker image with Node.js environment + "node", "-e", code, // Runs JavaScript code with Node.js + ) + + // Pass input to the JavaScript script + cmd.Stdin = bytes.NewBufferString(input) + + // Capture standard output and error output + var output bytes.Buffer + var errorOutput bytes.Buffer + cmd.Stdout = &output + cmd.Stderr = &errorOutput + + // Run the command + if err := cmd.Run(); err != nil { + return "", fmt.Sprintf("Command execution failed: %s: %v", errorOutput.String(), err), nil + } + + return strings.TrimSuffix(output.String(), "\n"), strings.TrimSuffix(errorOutput.String(), "\n"), nil +} diff --git a/apps/execution-service/execution/python/Dockerfile b/apps/execution-service/execution/python/Dockerfile new file mode 100644 index 0000000000..54ff4e8d0e --- /dev/null +++ b/apps/execution-service/execution/python/Dockerfile @@ -0,0 +1,3 @@ +FROM python:3.10-slim + +WORKDIR /app diff --git a/apps/execution-service/execution/python/python.go b/apps/execution-service/execution/python/python.go new file mode 100644 index 0000000000..726bc29d8b --- /dev/null +++ b/apps/execution-service/execution/python/python.go @@ -0,0 +1,33 @@ +package python + +import ( + "bytes" + "fmt" + "os/exec" + "strings" +) + +func RunPythonCode(code string, input string) (string, string, error) { + cmd := exec.Command( + "docker", "run", "--rm", + "-i", // allows for standard input to be passed in + "apps-python-sandbox", + "python", "-c", code, + ) + + // Pass in any input data to the Python script + cmd.Stdin = bytes.NewBufferString(input) + + // Capture output and error + var output bytes.Buffer + var errorOutput bytes.Buffer + cmd.Stdout = &output + cmd.Stderr = &errorOutput + + // Run the command + if err := cmd.Run(); err != nil { + return "", fmt.Sprintf("Command execution failed: %s: %v", errorOutput.String(), err), nil + } + + return strings.TrimSuffix(output.String(), "\n"), strings.TrimSuffix(errorOutput.String(), "\n"), nil +} diff --git a/apps/execution-service/go.mod b/apps/execution-service/go.mod new file mode 100644 index 0000000000..fa974a5550 --- /dev/null +++ b/apps/execution-service/go.mod @@ -0,0 +1,54 @@ +module execution-service + +go 1.23.1 + +require ( + cloud.google.com/go/firestore v1.17.0 + firebase.google.com/go/v4 v4.15.0 + github.com/go-chi/chi/v5 v5.1.0 + github.com/go-chi/cors v1.2.1 + github.com/joho/godotenv v1.5.1 + github.com/rabbitmq/amqp091-go v1.10.0 + github.com/traefik/yaegi v0.16.1 + google.golang.org/api v0.203.0 +) + +require ( + cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/auth v0.9.9 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect + cloud.google.com/go/compute/metadata v0.5.2 // indirect + cloud.google.com/go/iam v1.2.1 // indirect + cloud.google.com/go/longrunning v0.6.1 // indirect + cloud.google.com/go/storage v1.43.0 // indirect + github.com/MicahParks/keyfunc v1.9.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/s2a-go v0.1.8 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/time v0.7.0 // indirect + google.golang.org/appengine/v2 v2.0.2 // indirect + google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect +) diff --git a/apps/execution-service/go.sum b/apps/execution-service/go.sum new file mode 100644 index 0000000000..4733d88abb --- /dev/null +++ b/apps/execution-service/go.sum @@ -0,0 +1,201 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/auth v0.9.9 h1:BmtbpNQozo8ZwW2t7QJjnrQtdganSdmqeIBxHxNkEZQ= +cloud.google.com/go/auth v0.9.9/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= +cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/firestore v1.17.0 h1:iEd1LBbkDZTFsLw3sTH50eyg4qe8eoG6CjocmEXO9aQ= +cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y= +cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= +cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= +cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= +cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= +cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= +cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= +firebase.google.com/go/v4 v4.15.0 h1:k27M+cHbyN1YpBI2Cf4NSjeHnnYRB9ldXwpqA5KikN0= +firebase.google.com/go/v4 v4.15.0/go.mod h1:S/4MJqVZn1robtXkHhpRUbwOC4gdYtgsiMMJQ4x+xmQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= +github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= +github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= +github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E= +github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU= +google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine/v2 v2.0.2 h1:MSqyWy2shDLwG7chbwBJ5uMyw6SNqJzhJHNDwYB0Akk= +google.golang.org/appengine/v2 v2.0.2/go.mod h1:PkgRUWz4o1XOvbqtWTkBtCitEJ5Tp4HoVEdMMYQR/8E= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE= +google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53/go.mod h1:fheguH3Am2dGp1LfXkrvwqC/KlFq8F0nLq3LryOMrrE= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/apps/execution-service/handlers/create.go b/apps/execution-service/handlers/create.go new file mode 100644 index 0000000000..6a0d9d0324 --- /dev/null +++ b/apps/execution-service/handlers/create.go @@ -0,0 +1,119 @@ +package handlers + +import ( + "cloud.google.com/go/firestore" + "encoding/json" + "execution-service/models" + "execution-service/utils" + "google.golang.org/api/iterator" + "net/http" +) + +func (s *Service) CreateTest(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var test models.Test + if err := utils.DecodeJSONBody(w, r, &test); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Basic validation for question title and question ID + if test.QuestionDocRefId == "" || test.QuestionTitle == "" { + http.Error(w, "QuestionDocRefId and QuestionTitle are required", http.StatusBadRequest) + return + } + + // Normalise test cases + test.VisibleTestCases = utils.NormaliseTestCaseFormat(test.VisibleTestCases) + test.HiddenTestCases = utils.NormaliseTestCaseFormat(test.HiddenTestCases) + + // Automatically populate validation for input and output in test case + test.InputValidation = utils.GetDefaultValidation() + test.OutputValidation = utils.GetDefaultValidation() + + // Validate test case format + if _, err := utils.ValidateTestCaseFormat(test.VisibleTestCases, test.InputValidation, + test.OutputValidation); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if _, err := utils.ValidateTestCaseFormat(test.HiddenTestCases, test.InputValidation, + test.OutputValidation); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Check if a test already exists for the question + iter := s.Client.Collection("tests").Where("questionDocRefId", "==", test.QuestionDocRefId).Documents(ctx) + for { + _, err := iter.Next() + if err == iterator.Done { + break + } + if err != nil { + http.Error(w, "Error fetching test", http.StatusInternalServerError) + return + } + http.Error(w, "Test already exists for the question", http.StatusConflict) + return + } + defer iter.Stop() + + // Save test to Firestore + docRef, _, err := s.Client.Collection("tests").Add(ctx, map[string]interface{}{ + "questionDocRefId": test.QuestionDocRefId, + "questionTitle": test.QuestionTitle, + "visibleTestCases": test.VisibleTestCases, + "hiddenTestCases": test.HiddenTestCases, + "inputValidation": test.InputValidation, + "outputValidation": test.OutputValidation, + "createdAt": firestore.ServerTimestamp, + "updatedAt": firestore.ServerTimestamp, + }) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Get data + doc, err := docRef.Get(ctx) + if err != nil { + if err != iterator.Done { + http.Error(w, "Test not found", http.StatusInternalServerError) + return + } + http.Error(w, "Failed to get test", http.StatusInternalServerError) + return + } + + // Map data + if err := doc.DataTo(&test); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(test) +} + +// Manual test cases + +//curl -X POST http://localhost:8083/tests \ +//-H "Content-Type: application/json" \ +//-d '{ +//"questionDocRefId": "sampleDocRefId123", +//"questionTitle": "Sample Question Title", +//"visibleTestCases": "2\nhello\nolleh\nHannah\nhannaH", +//"hiddenTestCases": "2\nHannah\nhannaH\nabcdefg\ngfedcba" +//}' + +//curl -X POST http://localhost:8083/tests \ +//-H "Content-Type: application/json" \ +//-d "{ +//\"questionDocRefId\": \"sampleDocRefId12345\", +//\"questionTitle\": \"Sample Question Title\", +//\"visibleTestCases\": \"2\\nhello\\nolleh\\nHannah\\nhannaH\", +//\"hiddenTestCases\": \"2\\nHannah\\nhannaH\\nabcdefg\\ngfedcba\" +//}" diff --git a/apps/execution-service/handlers/delete.go b/apps/execution-service/handlers/delete.go new file mode 100644 index 0000000000..79b9987d3a --- /dev/null +++ b/apps/execution-service/handlers/delete.go @@ -0,0 +1,40 @@ +package handlers + +import ( + "github.com/go-chi/chi/v5" + "google.golang.org/api/iterator" + "net/http" +) + +func (s *Service) DeleteTest(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // Parse request + docRefID := chi.URLParam(r, "questionDocRefId") + + docRef := s.Client.Collection("tests").Where("questionDocRefId", "==", docRefID).Limit(1).Documents(ctx) + doc, err := docRef.Next() + if err != nil { + if err == iterator.Done { + http.Error(w, "Test not found", http.StatusNotFound) + return + } + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer docRef.Stop() + + _, err = doc.Ref.Delete(ctx) + if err != nil { + http.Error(w, "Error deleting test", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) +} + +// Manual test cases + +//curl -X DELETE http://localhost:8083/tests/sampleDocRefId123 \ +//-H "Content-Type: application/json" diff --git a/apps/execution-service/handlers/execute.go b/apps/execution-service/handlers/execute.go new file mode 100644 index 0000000000..a98ac201ea --- /dev/null +++ b/apps/execution-service/handlers/execute.go @@ -0,0 +1,69 @@ +package handlers + +import ( + "encoding/json" + "execution-service/models" + "execution-service/utils" + "github.com/go-chi/chi/v5" + "google.golang.org/api/iterator" + "net/http" +) + +func (s *Service) ExecuteVisibleAndCustomTests(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + questionDocRefId := chi.URLParam(r, "questionDocRefId") + if questionDocRefId == "" { + http.Error(w, "questionDocRefId is required", http.StatusBadRequest) + return + } + + var code models.Code + if err := utils.DecodeJSONBody(w, r, &code); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + iter := s.Client.Collection("tests").Where("questionDocRefId", "==", questionDocRefId).Limit(1).Documents(ctx) + doc, err := iter.Next() + if err != nil { + if err == iterator.Done { + http.Error(w, "Test not found", http.StatusNotFound) + return + } + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer iter.Stop() + + var test models.Test + if err := doc.DataTo(&test); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + testResults, err := utils.ExecuteVisibleAndCustomTests(code, test) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(testResults) +} + +//curl -X POST http://localhost:8083/tests/Yt29JjnIDpRwIlYAX8OF/execute \ +//-H "Content-Type: application/json" \ +//-d '{ +//"code": "name = input()\nprint(name[::-1])", +//"language": "Python" +//}' + +//curl -X POST http://localhost:8083/tests/Yt29JjnIDpRwIlYAX8OF/execute \ +//-H "Content-Type: application/json" \ +//-d '{ +//"code": "name = input()\nprint(name[::-1])", +//"language": "Python", +//"customTestCases": "2\nHannah\nhannaH\nabcdefg\ngfedcba\n" +//}' diff --git a/apps/execution-service/handlers/populate.go b/apps/execution-service/handlers/populate.go new file mode 100644 index 0000000000..014b4fe760 --- /dev/null +++ b/apps/execution-service/handlers/populate.go @@ -0,0 +1,31 @@ +package handlers + +import ( + "execution-service/models" + "execution-service/utils" + "net/http" +) + +func (s *Service) PopulateTests(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var questions []models.Question + if err := utils.DecodeJSONBody(w, r, &questions); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + questionTitleToDocRefIdMap := make(map[string]string) + for _, question := range questions { + questionTitleToDocRefIdMap[question.Title] = question.QuestionDocRefId + } + + err := utils.RepopulateTests(ctx, s.Client, questionTitleToDocRefIdMap) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) +} diff --git a/apps/execution-service/handlers/readall.go b/apps/execution-service/handlers/readall.go new file mode 100644 index 0000000000..4b622d5294 --- /dev/null +++ b/apps/execution-service/handlers/readall.go @@ -0,0 +1,71 @@ +package handlers + +import ( + "encoding/json" + "execution-service/models" + "execution-service/utils" + "net/http" + + "github.com/go-chi/chi/v5" + "google.golang.org/api/iterator" +) + +func (s *Service) ReadAllTests(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + questionDocRefId := chi.URLParam(r, "questionDocRefId") + if questionDocRefId == "" { + http.Error(w, "questionDocRefId is required", http.StatusBadRequest) + return + } + + iter := s.Client.Collection("tests").Where("questionDocRefId", "==", questionDocRefId).Limit(1).Documents(ctx) + doc, err := iter.Next() + if err != nil { + if err == iterator.Done { + http.Error(w, "Test not found", http.StatusNotFound) + return + } + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer iter.Stop() + + var test models.Test + if err := doc.DataTo(&test); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + _, hiddenTestCases, err := utils.GetTestLengthAndUnexecutedCases(test.HiddenTestCases) + + var hiddenTests []models.HiddenTest + for _, hiddenTestCase := range hiddenTestCases { + hiddenTests = append(hiddenTests, models.HiddenTest{ + Input: hiddenTestCase.Input, + Expected: hiddenTestCase.Expected, + }) + } + + _, visibleTestCases, err := utils.GetTestLengthAndUnexecutedCases(test.VisibleTestCases) + + var visibleTests []models.VisibleTest + for _, visibleTestCase := range visibleTestCases { + visibleTests = append(visibleTests, models.VisibleTest{ + Input: visibleTestCase.Input, + Expected: visibleTestCase.Expected, + }) + } + + allTests := models.AllTests{ + VisibleTests: visibleTests, + HiddenTests: hiddenTests, + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(allTests) +} + +//curl -X GET http://localhost:8083/tests/bmzFyLMeSOoYU99pi4yZ/ \ +//-H "Content-Type: application/json" diff --git a/apps/execution-service/handlers/readvisible.go b/apps/execution-service/handlers/readvisible.go new file mode 100644 index 0000000000..99e1a372c8 --- /dev/null +++ b/apps/execution-service/handlers/readvisible.go @@ -0,0 +1,55 @@ +package handlers + +import ( + "encoding/json" + "execution-service/models" + "execution-service/utils" + "github.com/go-chi/chi/v5" + "google.golang.org/api/iterator" + "net/http" +) + +func (s *Service) ReadVisibleTests(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + questionDocRefId := chi.URLParam(r, "questionDocRefId") + if questionDocRefId == "" { + http.Error(w, "questionDocRefId is required", http.StatusBadRequest) + return + } + + iter := s.Client.Collection("tests").Where("questionDocRefId", "==", questionDocRefId).Limit(1).Documents(ctx) + doc, err := iter.Next() + if err != nil { + if err == iterator.Done { + http.Error(w, "Test not found", http.StatusNotFound) + return + } + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer iter.Stop() + + var test models.Test + if err := doc.DataTo(&test); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + _, visibleTestCases, err := utils.GetTestLengthAndUnexecutedCases(test.VisibleTestCases) + + var visibleTests []models.VisibleTest + for _, visibleTestCase := range visibleTestCases { + visibleTests = append(visibleTests, models.VisibleTest{ + Input: visibleTestCase.Input, + Expected: visibleTestCase.Expected, + }) + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(visibleTests) +} + +//curl -X GET http://localhost:8083/tests/bmzFyLMeSOoYU99pi4yZ/ \ +//-H "Content-Type: application/json" diff --git a/apps/execution-service/handlers/server.go b/apps/execution-service/handlers/server.go new file mode 100644 index 0000000000..2d237dc025 --- /dev/null +++ b/apps/execution-service/handlers/server.go @@ -0,0 +1,9 @@ +package handlers + +import ( + "cloud.google.com/go/firestore" +) + +type Service struct { + Client *firestore.Client +} diff --git a/apps/execution-service/handlers/submit.go b/apps/execution-service/handlers/submit.go new file mode 100644 index 0000000000..1411ffcea7 --- /dev/null +++ b/apps/execution-service/handlers/submit.go @@ -0,0 +1,133 @@ +package handlers + +import ( + "encoding/json" + "execution-service/constants" + "execution-service/messagequeue" + "execution-service/models" + "execution-service/utils" + "fmt" + "net/http" + + "github.com/go-chi/chi/v5" + "google.golang.org/api/iterator" +) + +func (s *Service) ExecuteVisibleAndHiddenTestsAndSubmit(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + questionDocRefId := chi.URLParam(r, "questionDocRefId") + if questionDocRefId == "" { + http.Error(w, "questionDocRefId is required", http.StatusBadRequest) + return + } + + var submission models.Submission + if err := utils.DecodeJSONBody(w, r, &submission); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if err := validateSubmissionFields(submission); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + iter := s.Client.Collection("tests").Where("questionDocRefId", "==", questionDocRefId).Limit(1).Documents(ctx) + doc, err := iter.Next() + if err != nil { + if err == iterator.Done { + http.Error(w, "Test not found", http.StatusNotFound) + return + } + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer iter.Stop() + + var test models.Test + if err := doc.DataTo(&test); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + testResults, err := utils.ExecuteVisibleAndHiddenTests(models.Code{ + Code: submission.Code, + Language: submission.Language, + }, test) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Save the collaboration history via the history-service + submissionReq := models.Submission{ + Code: submission.Code, + Language: submission.Language, + User: submission.User, + MatchedUser: submission.MatchedUser, + MatchedTopics: submission.MatchedTopics, + Title: submission.Title, + QuestionDifficulty: submission.QuestionDifficulty, + QuestionTopics: submission.QuestionTopics, + } + submissionHistoryReq := models.SubmissionHistory{ + Submission: submissionReq, + QuestionDocRefID: questionDocRefId, + Status: testResults.Status, + } + + jsonData, err := json.Marshal(submissionHistoryReq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + err = messagequeue.PublishSubmissionMessage(jsonData) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to save submission history: %v", err), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(testResults) +} + +func validateSubmissionFields(submission models.Submission) error { + if submission.Title == "" { + return fmt.Errorf("title is required") + } + + if !constants.IS_VALID_LANGUAGE[submission.Language] { + return fmt.Errorf("invalid language") + } + + if submission.User == "" { + return fmt.Errorf("user is required") + } + + if submission.MatchedUser == "" { + return fmt.Errorf("matchedUser is required") + } + + if submission.QuestionDifficulty == "" { + return fmt.Errorf("questionDifficulty is required") + } + + return nil +} + +//curl -X POST http://localhost:8083/tests/Yt29JjnIDpRwIlYAX8OF/submit \ +//-H "Content-Type: application/json" \ +//-d '{ +//"title": "Example Title", +//"code": "name = input()\nprint(name[::-1])", +//"language": "Python", +//"user": "user123", +//"matchedUser": "user456", +//"matchId": "match123", +//"matchedTopics": ["topic1", "topic2"], +//"questionDifficulty": "Medium", +//"questionTopics": ["Loops", "Strings"] +//}' diff --git a/apps/execution-service/handlers/update.go b/apps/execution-service/handlers/update.go new file mode 100644 index 0000000000..b713a52c2c --- /dev/null +++ b/apps/execution-service/handlers/update.go @@ -0,0 +1,96 @@ +package handlers + +import ( + "cloud.google.com/go/firestore" + "encoding/json" + "execution-service/models" + "execution-service/utils" + "github.com/go-chi/chi/v5" + "google.golang.org/api/iterator" + "net/http" +) + +func (s *Service) UpdateTest(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // get param questionDocRefId + questionDocRefId := chi.URLParam(r, "questionDocRefId") + + var test models.Test + if err := utils.DecodeJSONBody(w, r, &test); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Normalise test cases + test.VisibleTestCases = utils.NormaliseTestCaseFormat(test.VisibleTestCases) + test.HiddenTestCases = utils.NormaliseTestCaseFormat(test.HiddenTestCases) + + // Only test cases will be updated + // Validate test case format with default validation + if _, err := utils.ValidateTestCaseFormat(test.VisibleTestCases, utils.GetDefaultValidation(), + utils.GetDefaultValidation()); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if _, err := utils.ValidateTestCaseFormat(test.HiddenTestCases, utils.GetDefaultValidation(), + utils.GetDefaultValidation()); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Update test in Firestore + docRef := s.Client.Collection("tests").Where("questionDocRefId", "==", questionDocRefId).Limit(1).Documents(ctx) + doc, err := docRef.Next() + if err != nil { + if err == iterator.Done { + http.Error(w, "Test not found", http.StatusNotFound) + return + } + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer docRef.Stop() + + // Update database + updates := []firestore.Update{ + {Path: "visibleTestCases", Value: test.VisibleTestCases}, + {Path: "hiddenTestCases", Value: test.HiddenTestCases}, + {Path: "updatedAt", Value: firestore.ServerTimestamp}, + } + _, err = doc.Ref.Update(ctx, updates) + if err != nil { + http.Error(w, "Error updating test", http.StatusInternalServerError) + return + } + + // Get data + doc, err = doc.Ref.Get(ctx) + if err != nil { + if err != iterator.Done { + http.Error(w, "Test not found", http.StatusNotFound) + return + } + http.Error(w, "Failed to get test", http.StatusInternalServerError) + return + } + + // Map data + if err = doc.DataTo(&test); err != nil { + http.Error(w, "Failed to map test data", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(test) +} + +// Manual test cases + +//curl -X PUT http://localhost:8083/tests/sampleDocRefId123 \ +//-H "Content-Type: application/json" \ +//-d '{ +//"visibleTestCases": "2\nhello\nolleh\nHannah\nhannaH", +//"hiddenTestCases": "2\nHannah\nhannaH\nabcdefg\ngfedcba" +//}' diff --git a/apps/execution-service/main.go b/apps/execution-service/main.go new file mode 100644 index 0000000000..7bf004153f --- /dev/null +++ b/apps/execution-service/main.go @@ -0,0 +1,114 @@ +package main + +import ( + "context" + "execution-service/handlers" + "execution-service/messagequeue" + "execution-service/utils" + "fmt" + "log" + "net/http" + "os" + "time" + + "cloud.google.com/go/firestore" + firebase "firebase.google.com/go/v4" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/cors" + "github.com/joho/godotenv" + "google.golang.org/api/option" +) + +func main() { + // Load .env file + err := godotenv.Load() + utils.FailOnError(err, "Error loading .env file") + + // Initialize Firestore client + ctx := context.Background() + client, err := initFirestore(ctx) + utils.FailOnError(err, "Failed to initialize Firestore client") + defer client.Close() + + service := &handlers.Service{Client: client} + + amqpConnection, amqpChannel := messagequeue.InitRabbitMQServer() + defer amqpConnection.Close() + defer amqpChannel.Close() + + r := initChiRouter(service) + initRestServer(r) +} + +// initFirestore initializes the Firestore client +func initFirestore(ctx context.Context) (*firestore.Client, error) { + credentialsPath := os.Getenv("FIREBASE_CREDENTIAL_PATH") + opt := option.WithCredentialsFile(credentialsPath) + app, err := firebase.NewApp(ctx, nil, opt) + if err != nil { + return nil, fmt.Errorf("failed to initialize Firebase App: %v", err) + } + + client, err := app.Firestore(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get Firestore client: %v", err) + } + return client, nil +} + +func initChiRouter(service *handlers.Service) *chi.Mux { + r := chi.NewRouter() + r.Use(middleware.Logger) + r.Use(middleware.Timeout(60 * time.Second)) + + r.Use(cors.Handler(cors.Options{ + // AllowedOrigins: []string{"http://localhost:3000"}, // Use this to allow specific origin hosts + AllowedOrigins: []string{"https://*", "http://*"}, + // AllowOriginFunc: func(r *http.Request, origin string) bool { return true }, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, + ExposedHeaders: []string{"Link"}, + AllowCredentials: false, + MaxAge: 300, // Maximum value not ignored by any of major browsers + })) + + registerRoutes(r, service) + + return r +} + +func registerRoutes(r *chi.Mux, service *handlers.Service) { + r.Route("/tests", func(r chi.Router) { + // Re: CreateTest + // Current: Unused, since testcases are populated via script + // Future extension: can be created by admin + r.Post("/", service.CreateTest) + r.Post("/populate", service.PopulateTests) + + r.Route("/{questionDocRefId}", func(r chi.Router) { + // Re: UpdateTest, DeleteTest + // Current: Unused, since testcases are executed within service and not exposed + // Future extension: can be read by admin to view testcases + r.Put("/", service.UpdateTest) + r.Delete("/", service.DeleteTest) + r.Get("/", service.ReadVisibleTests) + r.Get("/readall", service.ReadAllTests) + r.Post("/execute", service.ExecuteVisibleAndCustomTests) + r.Post("/submit", service.ExecuteVisibleAndHiddenTestsAndSubmit) + }) + }) +} + +func initRestServer(r *chi.Mux) { + // Serve on port 8080 + port := os.Getenv("PORT") + if port == "" { + port = "8083" + } + + // Start the server + log.Printf("Starting REST server on http://localhost:%s", port) + err := http.ListenAndServe(fmt.Sprintf(":%s", port), r) + utils.FailOnError(err, "Failed to start REST server") +} diff --git a/apps/execution-service/messagequeue/rabbitmq.go b/apps/execution-service/messagequeue/rabbitmq.go new file mode 100644 index 0000000000..1a6aafcde7 --- /dev/null +++ b/apps/execution-service/messagequeue/rabbitmq.go @@ -0,0 +1,78 @@ +package messagequeue + +import ( + "execution-service/utils" + "fmt" + "log" + "os" + "time" + + amqp "github.com/rabbitmq/amqp091-go" +) + +const ( + CODE_SUBMISSION_QUEUE_KEY = "code-submission" + NUM_RETRIES = 10 +) + +var ( + codeSubmissionQueue amqp.Queue + rabbitMQChannel *amqp.Channel +) + +func InitRabbitMQServer() (*amqp.Connection, *amqp.Channel) { + conn := connectToRabbitMQ() + + // Create a channel + ch, err := conn.Channel() + utils.FailOnError(err, "Failed to open a channel") + rabbitMQChannel = ch + + // Declare a queue + q, err := ch.QueueDeclare( + CODE_SUBMISSION_QUEUE_KEY, // name + false, // durable + false, // delete when unused + false, // exclusive + false, // no-wait + nil, // arguments + ) + utils.FailOnError(err, "Failed to declare a queue") + codeSubmissionQueue = q + + return conn, ch +} + +func connectToRabbitMQ() *amqp.Connection { + var conn *amqp.Connection + var err error + rabbitMQURL := os.Getenv("RABBITMQ_URL") + for i := 0; i < NUM_RETRIES; i++ { // Retry up to 10 times + conn, err = amqp.Dial(rabbitMQURL) + if err == nil { + log.Println("Connected to RabbitMQ") + return conn + } + log.Printf("Failed to connect to RabbitMQ, retrying in 5 seconds... (Attempt %d/%d)", i+1, NUM_RETRIES) + time.Sleep(5 * time.Second) + } + utils.FailOnError(err, fmt.Sprintf("Failed to connect to RabbitMQ after %d attempts", NUM_RETRIES)) + return nil +} + +func PublishSubmissionMessage(submission []byte) error { + err := rabbitMQChannel.Publish( + "", // exchange + codeSubmissionQueue.Name, // routing key + false, // mandatory + false, // immediate + amqp.Publishing{ + ContentType: "application/json", + Body: submission, + }) + if err != nil { + return fmt.Errorf("Failed to publish a message: %v", err) + } + log.Printf("RabbitMQ: [x] Sent %s", submission) + return nil +} diff --git a/apps/execution-service/models/code.go b/apps/execution-service/models/code.go new file mode 100644 index 0000000000..bd8ab16f84 --- /dev/null +++ b/apps/execution-service/models/code.go @@ -0,0 +1,7 @@ +package models + +type Code struct { + Code string `json:"code"` + Language string `json:"language"` + CustomTestCases string `json:"customTestCases"` +} diff --git a/apps/execution-service/models/question.go b/apps/execution-service/models/question.go new file mode 100644 index 0000000000..1a470c1839 --- /dev/null +++ b/apps/execution-service/models/question.go @@ -0,0 +1,6 @@ +package models + +type Question struct { + QuestionDocRefId string `json:"docRefId"` + Title string `json:"title"` +} diff --git a/apps/execution-service/models/submission.go b/apps/execution-service/models/submission.go new file mode 100644 index 0000000000..99bcc60ba5 --- /dev/null +++ b/apps/execution-service/models/submission.go @@ -0,0 +1,17 @@ +package models + +type Submission struct { + // Submission related details + Code string `json:"code" firestore:"code"` + Language string `json:"language" firestore:"language"` + + // Match related details + User string `json:"user" firestore:"user"` + MatchedUser string `json:"matchedUser" firestore:"matchedUser"` + MatchedTopics []string `json:"matchedTopics" firestore:"matchedTopics"` + + // Question related details + Title string `json:"title" firestore:"title"` + QuestionDifficulty string `json:"questionDifficulty" firestore:"questionDifficulty"` + QuestionTopics []string `json:"questionTopics" firestore:"questionTopics"` +} diff --git a/apps/execution-service/models/submissionHistory.go b/apps/execution-service/models/submissionHistory.go new file mode 100644 index 0000000000..17fb1b015e --- /dev/null +++ b/apps/execution-service/models/submissionHistory.go @@ -0,0 +1,16 @@ +package models + +import "time" + +type SubmissionHistory struct { + Submission + + // Additional question related details + QuestionDocRefID string `json:"questionDocRefId"` + Status string `json:"status"` + + // Special DB fields + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + HistoryDocRefID string `json:"historyDocRefId"` +} diff --git a/apps/execution-service/models/test.go b/apps/execution-service/models/test.go new file mode 100644 index 0000000000..9b6c6d80ac --- /dev/null +++ b/apps/execution-service/models/test.go @@ -0,0 +1,18 @@ +package models + +import "time" + +type Test struct { + QuestionDocRefId string `json:"questionDocRefId" firestore:"questionDocRefId"` + VisibleTestCases string `json:"visibleTestCases" firestore:"visibleTestCases"` + HiddenTestCases string `json:"hiddenTestCases" firestore:"hiddenTestCases"` + InputValidation string `json:"inputValidation" firestore:"inputValidation"` + OutputValidation string `json:"outputValidation" firestore:"outputValidation"` + + // Special DB fields + CreatedAt time.Time `json:"createdAt" firestore:"createdAt"` + UpdatedAt time.Time `json:"updatedAt" firestore:"updatedAt"` + + // Not stored in DB but used by json decoder + QuestionTitle string `json:"questionTitle" firestore:"questionTitle"` +} diff --git a/apps/execution-service/models/testResult.go b/apps/execution-service/models/testResult.go new file mode 100644 index 0000000000..1e812220d2 --- /dev/null +++ b/apps/execution-service/models/testResult.go @@ -0,0 +1,25 @@ +package models + +type ExecutionResults struct { + VisibleTestResults []IndividualTestResult `json:"visibleTestResults"` + CustomTestResults []IndividualTestResult `json:"customTestResults"` +} + +type SubmissionResults struct { + VisibleTestResults []IndividualTestResult `json:"visibleTestResults"` + HiddenTestResults GeneralTestResults `json:"hiddenTestResults"` + Status string `json:"status"` +} + +type IndividualTestResult struct { + Input string `json:"input"` + Expected string `json:"expected"` + Actual string `json:"actual"` + Passed bool `json:"passed"` + Error string `json:"error"` +} + +type GeneralTestResults struct { + Passed int `json:"passed"` + Total int `json:"total"` +} diff --git a/apps/execution-service/models/visibleTest.go b/apps/execution-service/models/visibleTest.go new file mode 100644 index 0000000000..2386df5dd2 --- /dev/null +++ b/apps/execution-service/models/visibleTest.go @@ -0,0 +1,16 @@ +package models + +type VisibleTest struct { + Input string `json:"input"` + Expected string `json:"expected"` +} + +type HiddenTest struct { + Input string `json:"input"` + Expected string `json:"expected"` +} + +type AllTests struct { + VisibleTests []VisibleTest `json:"visibleTests"` + HiddenTests []HiddenTest `json:"hiddenTests"` +} diff --git a/apps/execution-service/utils/decode.go b/apps/execution-service/utils/decode.go new file mode 100644 index 0000000000..981df9a617 --- /dev/null +++ b/apps/execution-service/utils/decode.go @@ -0,0 +1,20 @@ +package utils + +import ( + "encoding/json" + "fmt" + "net/http" +) + +// DecodeJSONBody decodes the request body into the given destination +func DecodeJSONBody(w http.ResponseWriter, r *http.Request, dst interface{}) error { + decoder := json.NewDecoder(r.Body) + decoder.DisallowUnknownFields() + + err := decoder.Decode(&dst) + if err != nil { + return fmt.Errorf("Invalid request payload: " + err.Error()) + } + + return nil +} diff --git a/apps/execution-service/utils/executeTest.go b/apps/execution-service/utils/executeTest.go new file mode 100644 index 0000000000..f2bfd1cf50 --- /dev/null +++ b/apps/execution-service/utils/executeTest.go @@ -0,0 +1,157 @@ +package utils + +import ( + "execution-service/constants" + "execution-service/execution/node" + "execution-service/execution/python" + "execution-service/models" + "fmt" +) + +func ExecuteVisibleAndCustomTests(code models.Code, test models.Test) (models.ExecutionResults, error) { + var err error + var testResults models.ExecutionResults + + switch code.Language { + case constants.PYTHON: + testResults, err = getVisibleAndCustomTestResults(code, test, python.RunPythonCode) + break + case constants.JAVASCRIPT: + testResults, err = getVisibleAndCustomTestResults(code, test, node.RunJavaScriptCode) + default: + return models.ExecutionResults{}, fmt.Errorf("unsupported language: %s", code.Language) + } + if err != nil { + return models.ExecutionResults{}, err + } + + return testResults, nil +} + +func ExecuteVisibleAndHiddenTests(code models.Code, test models.Test) (models.SubmissionResults, error) { + var err error + var testResults models.SubmissionResults + + switch code.Language { + case constants.PYTHON: + testResults, err = getVisibleAndHiddenTestResults(code, test, python.RunPythonCode) + break + case constants.JAVASCRIPT: + testResults, err = getVisibleAndHiddenTestResults(code, test, node.RunJavaScriptCode) + default: + return models.SubmissionResults{}, fmt.Errorf("unsupported language: %s", code.Language) + } + if err != nil { + return models.SubmissionResults{}, err + } + + return testResults, nil +} + +func getIndividualTestResultFromCodeExecutor(code string, unexecutedTestResult models.IndividualTestResult, + executor func(string, string) (string, string, error)) (models.IndividualTestResult, error) { + output, outputErr, err := executor(code, unexecutedTestResult.Input) + if err != nil { + return models.IndividualTestResult{}, err + } + return models.IndividualTestResult{ + Input: unexecutedTestResult.Input, + Expected: unexecutedTestResult.Expected, + Actual: output, + Passed: output == unexecutedTestResult.Expected, + Error: outputErr, + }, nil +} + +func getAllTestResultsFromFormattedTestCase(code string, testCase string, + executor func(string, string) (string, string, error)) ([]models.IndividualTestResult, error) { + _, testResults, err := GetTestLengthAndUnexecutedCases(testCase) + if err != nil { + return nil, err + } + + for i := range testResults { + currTestResult, err := getIndividualTestResultFromCodeExecutor(code, testResults[i], executor) + if err != nil { + return nil, err + } + testResults[i].Actual = currTestResult.Actual + testResults[i].Passed = currTestResult.Passed + testResults[i].Error = currTestResult.Error + } + + return testResults, nil +} + +func getGenericTestResultsFromFormattedTestCase(code string, testCase string, + executor func(string, string) (string, string, error)) (models.GeneralTestResults, error) { + testResults, err := getAllTestResultsFromFormattedTestCase(code, testCase, executor) + if err != nil { + return models.GeneralTestResults{}, err + } + + var passed int + for _, testResult := range testResults { + if testResult.Passed { + passed++ + } + } + + return models.GeneralTestResults{ + Passed: passed, + Total: len(testResults), + }, nil +} + +func getVisibleAndCustomTestResults(code models.Code, test models.Test, + executor func(string, string) (string, string, error)) (models.ExecutionResults, error) { + visibleTestResults, err := getAllTestResultsFromFormattedTestCase(code.Code, test.VisibleTestCases, executor) + if err != nil { + return models.ExecutionResults{}, err + } + + var customTestResults []models.IndividualTestResult + if code.CustomTestCases != "" { + customTestResults, err = getAllTestResultsFromFormattedTestCase(code.Code, code.CustomTestCases, executor) + if err != nil { + return models.ExecutionResults{}, err + } + } + + return models.ExecutionResults{ + VisibleTestResults: visibleTestResults, + CustomTestResults: customTestResults, + }, nil +} + +func getVisibleAndHiddenTestResults(code models.Code, test models.Test, + executor func(string, string) (string, string, error)) (models.SubmissionResults, error) { + visibleTestResults, err := getAllTestResultsFromFormattedTestCase(code.Code, test.VisibleTestCases, executor) + if err != nil { + return models.SubmissionResults{}, err + } + + hiddenTestResults, err := getGenericTestResultsFromFormattedTestCase(code.Code, test.HiddenTestCases, executor) + if err != nil { + return models.SubmissionResults{}, err + } + + status := constants.ACCEPTED + if hiddenTestResults.Passed != hiddenTestResults.Total { + status = constants.ATTEMPTED + } + if status == constants.ACCEPTED { + for _, testResult := range visibleTestResults { + if !testResult.Passed { + status = constants.ATTEMPTED + break + } + } + } + + return models.SubmissionResults{ + VisibleTestResults: visibleTestResults, + HiddenTestResults: hiddenTestResults, + Status: status, + }, nil +} diff --git a/apps/execution-service/utils/log.go b/apps/execution-service/utils/log.go new file mode 100644 index 0000000000..a77b2c29ca --- /dev/null +++ b/apps/execution-service/utils/log.go @@ -0,0 +1,9 @@ +package utils + +import "log" + +func FailOnError(err error, msg string) { + if err != nil { + log.Fatalf("%s: %s", msg, err) + } +} diff --git a/apps/execution-service/utils/populate.go b/apps/execution-service/utils/populate.go new file mode 100644 index 0000000000..767b565af2 --- /dev/null +++ b/apps/execution-service/utils/populate.go @@ -0,0 +1,681 @@ +package utils + +import ( + "cloud.google.com/go/firestore" + "context" + "execution-service/models" + "fmt" + "google.golang.org/api/iterator" + "log" + "strings" +) + +// RepopulateTests Populate deletes all testcases and repopulates testcases +func RepopulateTests(ctx context.Context, client *firestore.Client, + questionTitleToDocRefIdMap map[string]string) error { + + // delete all existing document in the collection + iter := client.Collection("tests").Documents(ctx) + for { + doc, err := iter.Next() + if err == iterator.Done { + break + } + if err != nil { + return fmt.Errorf("failed to iterate over tests: %v", err) + } + + if _, err := doc.Ref.Delete(ctx); err != nil { + return fmt.Errorf("failed to delete test: %v", err) + } + } + defer iter.Stop() + + var tests = []models.Test{ + { + QuestionTitle: "Reverse a String", + VisibleTestCases: ` +2 +hello +olleh +Hannah +hannaH +`, + HiddenTestCases: ` +2 +Hannah +hannaH +abcdefg +gfedcba +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + { + QuestionTitle: "Linked List Cycle Detection", + VisibleTestCases: ` +2 +[3,2,0,-4] -> pos = 1 +true +[1] +false +`, + HiddenTestCases: ` +2 +[1,2] -> pos = 0 +true +[1] +false +`, + InputValidation: getPackagesAndFunction([]string{"strconv", "strings"}, ` +hasCycle := false + +// Step 1: Split by " -> pos = " +parts := strings.Split(inputOrOutput, " -> pos = ") +if len(parts) == 2 { + hasCycle = true +} else if len(parts) != 1 { + return false +} + +listPart := strings.TrimSpace(parts[0]) // Get the list part + +//Validate the list format +if len(listPart) < 2 || listPart[0] != '[' || listPart[len(listPart)-1] != ']' { + return false +} + +listContent := listPart[1 : len(listPart)-1] // Remove brackets +if listContent == "" { // Check for empty list + return false +} + +// Split the list by commas and validate each element +elements := strings.Split(listContent, ",") +for _, elem := range elements { + elem = strings.TrimSpace(elem) // Trim whitespace + if _, err := strconv.Atoi(elem); err != nil { // Check if it's an integer + return false + } +} + +if !hasCycle { + return true +} + +posPart := strings.TrimSpace(parts[1]) // Get the position part + +//Validate the position +posValue, err := strconv.Atoi(posPart) +if err != nil || posValue < 0 || posValue >= len(elements) { + return false +} + +return true +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return inputOrOutput == "true" || inputOrOutput == "false" +`), + }, + { + QuestionTitle: "Roman to Integer", + VisibleTestCases: ` +2 +III +3 +IV +4 +`, + HiddenTestCases: ` +2 +IV +4 +MCMXCIV +1994 +`, + InputValidation: getPackagesAndFunction([]string{"strings"}, ` +valid := "IVXLCDM" +for _, char := range inputOrOutput { + if !strings.ContainsRune(valid, char) { + return false + } +} +return true +`), + OutputValidation: getPackagesAndFunction([]string{"strconv"}, ` +_, err := strconv.Atoi(inputOrOutput) +return err == nil +`), + }, + { + QuestionTitle: "Add Binary", + VisibleTestCases: ` +2 +"11", "1" +"100" +"1010", "1011" +"10101" +`, + HiddenTestCases: ` +2 +"1010", "1011" +"10101" +"111", "111" +"1110" +`, + InputValidation: getPackagesAndFunction([]string{"regexp"}, ` +binaryRegex := regexp.MustCompile("^\"([01]+)\",\\s*\"([01]+)\"$") +return binaryRegex.MatchString(inputOrOutput) +`), + OutputValidation: getPackagesAndFunction([]string{"regexp"}, ` +binaryRegex := regexp.MustCompile("^\"([01]+)\"$") +return binaryRegex.MatchString(inputOrOutput) +`), + }, + { + QuestionTitle: "Fibonacci Number", + VisibleTestCases: ` +2 +0 +0 +10 +55 +`, + HiddenTestCases: ` +2 +1 +1 +10 +55 +`, + InputValidation: getPackagesAndFunction([]string{"strconv"}, ` +num, err := strconv.Atoi(inputOrOutput) +return err == nil && num >= 0 +`), + OutputValidation: getPackagesAndFunction([]string{"strconv"}, ` +num, err := strconv.Atoi(inputOrOutput) +return err == nil && num >= 0 +`), + }, + { + QuestionTitle: "Implement Stack using Queues", + VisibleTestCases: ` +2 +push(1), push(2), top() +2 +push(1), empty() +false +`, + HiddenTestCases: ` +2 +push(1), push(2), pop(), top() +1 +push(1), empty() +false +`, + InputValidation: getPackagesAndFunction([]string{"strconv", "strings"}, ` +// Split the line by commas to handle multiple operations +operations := strings.Split(inputOrOutput, ",") +for _, op := range operations { + op = strings.TrimSpace(op) // Trim whitespace + // Check if the operation is valid + if strings.HasPrefix(op, "push(") && strings.HasSuffix(op, ")") { + // Check if it's a push operation with a valid integer + numStr := op[5 : len(op)-1] // Extract the number string + if _, err := strconv.Atoi(numStr); err != nil { + return false + } + } else if op != "pop()" && op != "top()" && op != "empty()" { + // If the operation is not one of the valid ones + return false + } +} +return true +`), + OutputValidation: getPackagesAndFunction([]string{"strconv"}, ` +if inputOrOutput == "true" || inputOrOutput == "false" || inputOrOutput == "null" { + return true +} +_, err := strconv.Atoi(inputOrOutput) +return err == nil +`), + }, { + QuestionTitle: "Combine Two Tables", + VisibleTestCases: ` +2 +Person: [(1, "Smith", "John"), (2, "Doe", "Jane")], Address: [(1, 1, "NYC", "NY"), (2, 3, "LA", "CA")] +[("John", "Smith", "NYC", "NY"), ("Jane", "Doe", null, null)] +Person: [(1, "White", "Mary")], Address: [] +[("Mary", "White", null, null)] +`, + HiddenTestCases: ` +2 +Person: [(1, "Black", "Jim")], Address: [(1, 1, "Miami", "FL")] +[("Jim", "Black", "Miami", "FL")] +Person: [(1, "White", "Mary")], Address: [] +[("Mary", "White", null, null)] +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + { + QuestionTitle: "Repeated DNA Sequences", + VisibleTestCases: ` +2 +AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT +["AAAAACCCCC", "CCCCCAAAAA"] +ACGTACGTACGT +[] +`, + HiddenTestCases: ` +2 +AAAAAAAAAAAAA +["AAAAAAAAAA"] +ACGTACGTACGT +[] +`, + InputValidation: getPackagesAndFunction([]string{}, ` +input := inputOrOutput + +// Check that input length is at least 10 +if len(input) < 10 { + return false +} + +// Check that input contains only 'A', 'C', 'G', and 'T' +for _, ch := range input { + if ch != 'A' && ch != 'C' && ch != 'G' && ch != 'T' { + return false + } +} +return true +`), + OutputValidation: getPackagesAndFunction([]string{"strings"}, ` +output := inputOrOutput + +if output == "[]" { + return true +} + +// Check that the output is enclosed in square brackets +if len(output) < 2 || output[0] != '[' || output[len(output)-1] != ']' { + return false +} + +// Extract the content between square brackets +content := output[1 : len(output)-1] + +// Split by commas without trimming spaces +sequences := strings.Split(content, ", ") +for _, seq := range sequences { + // Check if each sequence is properly enclosed in double quotes and is exactly 10 characters + if len(seq) != 12 || seq[0] != '"' || seq[11] != '"' { + return false + } + + // Check that the sequence only contains valid DNA characters between the quotes + for i := 1; i < 11; i++ { + ch := seq[i] + if ch != 'A' && ch != 'C' && ch != 'G' && ch != 'T' { + return false + } + } +} +return true +`), + }, + { + QuestionTitle: "Course Schedule", + VisibleTestCases: ` +2 +2, [[1,0]] +true +2, [[1,0],[0,1]] +false +`, + HiddenTestCases: ` +2 +2, [[1,0],[0,1]] +false +4, [[1,0],[2,0],[3,1],[3,2]] +true +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + { + QuestionTitle: "LRU Cache Design", + VisibleTestCases: ` +2 +put(1, 1), put(2, 2), get(1) +1 +put(1, 1), put(2, 2), put(3, 3), get(2) +-1 +`, + HiddenTestCases: ` +2 +put(1, 1), put(2, 2), put(3, 3), get(2) +-1 +put(1, 1), put(2, 2), put(1, 10), get(1) +10 +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + { + QuestionTitle: "Longest Common Subsequence", + VisibleTestCases: ` +2 +"abcde", "ace" +3 +"abc", "def" +0 +`, + HiddenTestCases: ` +2 +"abc", "abc" +3 +"abc", "def" +0 +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + { + QuestionTitle: "Rotate Image", + VisibleTestCases: ` +2 +[[1,2,3],[4,5,6],[7,8,9]] +[[7,4,1],[8,5,2],[9,6,3]] +[[1]] +[[1]] +`, + HiddenTestCases: ` +2 +[[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]] +[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]] +[[1]] +[[1]] +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + { + QuestionTitle: "Airplane Seat Assignment Probability", + VisibleTestCases: ` +2 +1 +1.00000 +3 +0.50000 +`, + HiddenTestCases: ` +2 +2 +0.50000 +3 +0.50000 +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + { + QuestionTitle: "Validate Binary Search Tree", + VisibleTestCases: ` +2 +[2,1,3] +true +[5,1,4,null,null,3,6] +false +`, + HiddenTestCases: ` +2 +[5,1,4,null,null,3,6] +false +[1] +true +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + { + QuestionTitle: "Sliding Window Maximum", + VisibleTestCases: ` +2 +[1,3,-1,-3,5,3,6,7], k=3 +[3,3,5,5,6,7] +[9, 11], k=2 +[11] +`, + HiddenTestCases: ` +2 +[1, -1], k=1 +[1, -1] +[9, 11], k=2 +[11] +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + { + QuestionTitle: "N-Queen Problem", + VisibleTestCases: ` +2 +4 +[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] +2 +[] +`, + HiddenTestCases: ` +2 +1 +[["Q"]] +2 +[] +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + { + QuestionTitle: "Serialize and Deserialize a Binary Tree", + VisibleTestCases: ` +2 +[1,2,3,null,null,4,5] +"1 2 null null 3 4 null null 5 null null" +[] +"null" +`, + HiddenTestCases: ` +2 +[] +"null" +[1] +"1 null null" +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + { + QuestionTitle: "Wildcard Matching", + VisibleTestCases: ` +2 +"aa", "a" +false +"aa", "*" +true +`, + HiddenTestCases: ` +2 +"aa", "*" +true +"cb", "?a" +false +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + { + QuestionTitle: "Chalkboard XOR Game", + VisibleTestCases: ` +2 +[1,1,2] +false +[1,2,3] +true +`, + HiddenTestCases: ` +2 +[1,2,3] +true +[0,0,0] +true +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + { + QuestionTitle: "Trips and Users", + VisibleTestCases: ` +2 +Trips: [(1, 1, 10, 'NYC', 'completed', '2013-10-01'), (2, 2, 11, 'NYC', 'cancelled_by_driver', '2013-10-01')],Users: [(10, 'No', 'client'), (11, 'No', 'driver')] +0.50 +Trips: [(1, 1, 10, 'NYC', 'completed', '2013-10-03'), (2, 2, 11, 'NYC', 'cancelled_by_client', '2013-10-03')],Users: [(10, 'No', 'client'), (11, 'Yes', 'driver')] +0.00 +`, + HiddenTestCases: ` +2 +Trips: [(1, 1, 10, 'NYC', 'completed', '2013-10-02')],Users: [(10, 'No', 'client'), (11, 'No', 'driver')] +0.00 +Trips: [(1, 1, 10, 'NYC', 'completed', '2013-10-03'), (2, 2, 11, 'NYC', 'cancelled_by_client', '2013-10-03')],Users: [(10, 'No', 'client'), (11, 'Yes', 'driver')] +0.00 +`, + InputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + OutputValidation: getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`), + }, + } + + for _, test := range tests { + if _, exists := questionTitleToDocRefIdMap[test.QuestionTitle]; !exists { + return fmt.Errorf("testcase's question title %s not found in questionTitleToDocRefIdMap", + test.QuestionTitle) + } + } + + for _, test := range tests { + _, err := ValidateTestCaseFormat(test.VisibleTestCases, test.InputValidation, test.OutputValidation) + if err != nil { + return fmt.Errorf("failed to validate visible test case format: %v", err) + } + _, err = ValidateTestCaseFormat(test.HiddenTestCases, test.InputValidation, test.OutputValidation) + if err != nil { + return fmt.Errorf("failed to validate hidden test case format: %v", err) + } + } + + for _, test := range tests { + _, _, err := client.Collection("tests").Add(ctx, map[string]interface{}{ + "questionDocRefId": questionTitleToDocRefIdMap[test.QuestionTitle], + "visibleTestCases": test.VisibleTestCases, + "hiddenTestCases": test.HiddenTestCases, + "createdAt": firestore.ServerTimestamp, + "updatedAt": firestore.ServerTimestamp, + }) + if err != nil { + return fmt.Errorf("failed to add test: %v", err) + } + } + + log.Println("Cleaned tests collection and repopulated tests.") + return nil +} + +func getPackagesAndFunction(imports []string, functionBody string) string { + // Use a string builder for efficient string concatenation + var importCode strings.Builder + + if len(imports) > 0 { + // Start the import block + importCode.WriteString("import (\n") + + // Iterate over the imports and add them to the builder + for _, imp := range imports { + importCode.WriteString(fmt.Sprintf("\t%q\n", imp)) + } + + // Close the import block + importCode.WriteString(")") + } + + // add tab before every line in functionBody including first line + functionBody = strings.ReplaceAll(functionBody, "\n", "\n\t") + + return fmt.Sprintf(` +%s + +func validateInputOrOutput(inputOrOutput string) bool { +%s +} +`, importCode.String(), functionBody) +} + +func GetDefaultValidation() string { + return getPackagesAndFunction([]string{}, ` +return len(inputOrOutput) > 0 +`) +} diff --git a/apps/execution-service/utils/testCase.go b/apps/execution-service/utils/testCase.go new file mode 100644 index 0000000000..c353f98220 --- /dev/null +++ b/apps/execution-service/utils/testCase.go @@ -0,0 +1,45 @@ +package utils + +import ( + "execution-service/models" + "fmt" + "strconv" + "strings" +) + +func GetTestLength(testCase string) (int, error) { + lines := strings.Split(strings.TrimSpace(testCase), "\n") + if len(lines) < 1 { + return 0, fmt.Errorf("test case format is incorrect, no lines found") + } + numCases, err := strconv.Atoi(lines[0]) + if err != nil { + return 0, fmt.Errorf("test case format is incorrect, first line must be an integer") + } + return numCases, nil +} + +func GetTestLengthAndUnexecutedCases(testCase string) (int, []models.IndividualTestResult, error) { + lines := strings.Split(strings.TrimSpace(testCase), "\n") + if len(lines) < 1 { + return 0, nil, fmt.Errorf("test case format is incorrect, no lines found") + } + + numCases, err := strconv.Atoi(lines[0]) + if err != nil { + return 0, nil, fmt.Errorf("test case format is incorrect, first line must be an integer") + } + + if len(lines) != 1+2*numCases { + return 0, nil, fmt.Errorf("test case format is incorrect, expected %d lines but got %d", 1+2*numCases, len(lines)) + } + + var testResults []models.IndividualTestResult + for i := 1; i < len(lines); i += 2 { + testResults = append(testResults, models.IndividualTestResult{ + Input: lines[i], + Expected: lines[i+1], + }) + } + return numCases, testResults, nil +} diff --git a/apps/execution-service/utils/validateTestCaseFormat.go b/apps/execution-service/utils/validateTestCaseFormat.go new file mode 100644 index 0000000000..5410a0e4f0 --- /dev/null +++ b/apps/execution-service/utils/validateTestCaseFormat.go @@ -0,0 +1,83 @@ +package utils + +import ( + "fmt" + "strconv" + "strings" + + "github.com/traefik/yaegi/interp" + "github.com/traefik/yaegi/stdlib" +) + +func ValidateTestCaseFormat(testCase string, validateInputCode string, validateOutputCode string) (bool, error) { + lines := strings.Split(strings.TrimSpace(testCase), "\n") + + // Check if there is at least one line (the number of test cases line) + if len(lines) < 1 { + return false, fmt.Errorf("test case format is incorrect, no lines found") + } + + // Parse the first line to get the expected number of test cases + numCases, err := strconv.Atoi(lines[0]) + if err != nil { + return false, fmt.Errorf("test case format is incorrect, first line must be an integer") + } + + // Calculate the required number of lines: 1 for count + 2 lines per test case + expectedLines := 1 + 2*numCases + if len(lines) != expectedLines { + return false, fmt.Errorf("test case format is incorrect, expected %d lines but got %d", expectedLines, + len(lines)) + } + + for i := 1; i < len(lines); i += 2 { + ok, err := validateInputOrOutputFormat(validateInputCode, lines[i]) + if err != nil { + return false, fmt.Errorf("error validating input: %v", err) + } + if !ok { + return false, fmt.Errorf("test case format is incorrect, input format is invalid") + } + ok, err = validateInputOrOutputFormat(validateOutputCode, lines[i+1]) + if err != nil { + return false, fmt.Errorf("error validating output: %v", err) + } + if !ok { + return false, fmt.Errorf("test case format is incorrect, output format is invalid") + } + } + + return true, nil +} + +func validateInputOrOutputFormat(validateInputOrOutputCode string, inputOrOutput string) (bool, error) { + // Initialize the yaegi interpreter + i := interp.New(interp.Options{}) + i.Use(stdlib.Symbols) + + // Create a Go function as a string and include the provided validation code + fullCode := fmt.Sprintf(` +package main +%s +`, validateInputOrOutputCode) + + // Evaluate the function code + _, err := i.Eval(fullCode) + if err != nil { + return false, fmt.Errorf("error evaluating code: %v", err) + } + + // Retrieve the validateInput function from the interpreter + validateFunc, err := i.Eval("main.validateInputOrOutput") + if err != nil { + return false, fmt.Errorf("validateInputOrOutput function not found") + } + + // Call the function with the provided input + result := validateFunc.Interface().(func(string) bool)(inputOrOutput) + return result, nil +} + +func NormaliseTestCaseFormat(testCase string) string { + return strings.ReplaceAll(testCase, "\\n", "\n") +} diff --git a/apps/frontend/.env.example b/apps/frontend/.env.example index 547a68c4c0..150a4cd1f6 100644 --- a/apps/frontend/.env.example +++ b/apps/frontend/.env.example @@ -1,4 +1,7 @@ # URL endpoints of the services NEXT_PUBLIC_QUESTION_SERVICE_URL="http://localhost:8080/" NEXT_PUBLIC_USER_SERVICE_URL="http://localhost:3001/" -NEXT_PUBLIC_MATCHING_SERVICE_URL="ws://localhost:8081/match" \ No newline at end of file +NEXT_PUBLIC_MATCHING_SERVICE_URL="ws://localhost:8081/match" +NEXT_PUBLIC_SIGNALLING_SERVICE_URL="ws://localhost:4444/" +NEXT_PUBLIC_HISTORY_SERVICE_URL="http://localhost:8082/" +NEXT_PUBLIC_EXECUTION_SERVICE_URL="http://localhost:8083/" \ No newline at end of file diff --git a/apps/frontend/README.md b/apps/frontend/README.md index 2572193fb1..a763465b0d 100644 --- a/apps/frontend/README.md +++ b/apps/frontend/README.md @@ -1,11 +1,27 @@ -This is the frontend for the question service. +# Frontend -## Tech Stack +![Home page](../../docs/home_page.png) -- Next.js -- TypeScript -- Ant Design -- SCSS +### Tech Stack + +- React: React is one of the most popular UI libraries that allows the creation of reusable UI functional components. Its community ecosystem also offers React hooks that simplify the implementation of some of our frontend components, such as websockets. +- Next.js: A React framework for building single-page applications. It comes with several useful features such as automatic page routing based on filesystem. +- Ant Design: An enterprise-level design system that comes with several extensible UI components and solutions out-of-the-box, which allows us to quickly create nice-looking components that can be adjusted according to our requirements. +- Typescript: A language extension of Javascript that allows us to perform static type-checking, to ensure that issues with incorrectly used types are caught and resolved as early as possible, improving code maintainability. + +### Authorization-based Route Protection with Next.js Middleware + +Middleware is a Next.js feature that allows the webpage server to intercept page requests and perform checks before serving the webpage. We used this feature to protect page access from unauthenticated users. This was done by checking the request’s JWT token (passed as a cookie) against the user service and redirecting users without authorized access to a public route (namely, the login page). + +### User Flow and Communication between Microservices + +Clients interact with the microservices through dedicated endpoints, with each microservice managing its own database for independent reading and writing. + +Having individual databases per microservice improves data security, scalability, fault isolation, flexibility in database choice, and development efficiency. This approach allows each microservice to operate independently, optimizing stability, performance, and adaptability in the system. + +![Diagram for user flow and communication between microservices](../../docs/userflow.png) + +--- ## Getting Started diff --git a/apps/frontend/__tests__/browser-tests/browser.test.ts b/apps/frontend/__tests__/browser-tests/browser.test.ts new file mode 100644 index 0000000000..145c036b81 --- /dev/null +++ b/apps/frontend/__tests__/browser-tests/browser.test.ts @@ -0,0 +1,82 @@ +import { Actions, Browser, Builder, By, Capabilities, Key, until, WebDriver } from "selenium-webdriver" + +import {Options as ChromeOptions} from "selenium-webdriver/chrome" +import {Options as EdgeOptions} from "selenium-webdriver/edge" +import {Options as FirefoxOptions} from "selenium-webdriver/firefox" + +const URL = 'http://localhost:3000/'; +const ETERNAL_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjk5OTk5OTk5OTk5fQ.Z4_FVGQ5lIcouP3m4YLMr6pGMF17IJFfo2yOTiN58DY" + +const CHROME_OPTIONS = new ChromeOptions() + .addArguments("--headless=new") as ChromeOptions; // uncomment locally to see the steps in action +const EDGE_OPTIONS = new EdgeOptions() + .setBinaryPath("/opt/hostedtoolcache/msedge/stable/x64/msedge") // need to point to the correct path + .addArguments("--headless=new") as EdgeOptions; + +const FIREFOX_OPTIONS = new FirefoxOptions() + .addArguments("--headless") as FirefoxOptions; + +const builder = new Builder() + .setChromeOptions(CHROME_OPTIONS) + .setEdgeOptions(EDGE_OPTIONS) + .setFirefoxOptions(FIREFOX_OPTIONS) + +describe.each([Browser.CHROME, Browser.EDGE, Browser.FIREFOX])("%s driver test", (browser) => { + let driver: WebDriver; + beforeAll(() => { + const cap = new Capabilities().setBrowserName(browser) + builder.withCapabilities(cap); + }) + + beforeEach(async () => { + console.log(browser + ": building..."); + driver = await builder.build(); + console.log(browser + ": built"); + }, 20000) + + afterEach(async () => { + if (driver) { + await driver.quit(); + } + }) + + describe("webdriver installed correctly", () => { + it("does google search", async () => { + await driver.get('http://www.google.com'); + await driver.findElement(By.name('q')).sendKeys('webdriver', Key.RETURN); + await driver.wait(until.titleIs('webdriver - Google Search'), 1000); + }, 10000); + + it.skip("does another google search", async () => { + await driver.get('http://www.google.com'); + await driver.findElement(By.name('q')).sendKeys('webdriver', Key.RETURN); + await driver.wait(until.titleIs('webdriver - Google Search'), 1000); + }, 10000); + }); + + describe("browser-test", () => { + it("accesses and login to peerprep", async () => { + await driver.get(URL); + await driver.wait(until.urlIs(`${URL}login`)); + + const [email, password] = await driver.findElements(By.css("input")) + const submit = await driver.findElement(By.css("button[type=\"submit\"]")) + + await email.sendKeys("admin@gmail.com"); + await password.sendKeys("admin"); + + await submit.click(); + await driver.wait(until.urlIs(`${URL}`)); + + const slogan1 = await driver.findElement(By.xpath("/html/body/div[1]/main/div/div[1]/div[2]/span[1]")).then(ele => ele.getText()) + const slogan2 = await driver.findElement(By.xpath("/html/body/div[1]/main/div/div[1]/div[2]/span[2]")).then(ele => ele.getText()) + + expect(slogan1).toBe("A better way to prepare for coding interviews with"); + expect(slogan2).toBe("peers"); + }, 10000); + }) +}, 60000) + + + + diff --git a/apps/frontend/__tests__/unit-tests/Datetime.test.ts b/apps/frontend/__tests__/unit-tests/Datetime.test.ts new file mode 100644 index 0000000000..8737f9ebe3 --- /dev/null +++ b/apps/frontend/__tests__/unit-tests/Datetime.test.ts @@ -0,0 +1,9 @@ +import { formatTime } from "@/utils/DateTime" + +describe("datetime module", () => { + it("formats a time correctly", () => { + expect(formatTime(10)).toBe("00:10") + }); +}) + + diff --git a/apps/frontend/__tests__/unit-tests/dependencymocking.test.ts b/apps/frontend/__tests__/unit-tests/dependencymocking.test.ts new file mode 100644 index 0000000000..0f21af7f43 --- /dev/null +++ b/apps/frontend/__tests__/unit-tests/dependencymocking.test.ts @@ -0,0 +1,34 @@ +import { GetQuestions } from "@/app/services/question" +import { getToken } from "@/app/services/login-store"; + +const TOKEN = 'mocktoken'; + +jest.mock("@/app/services/login-store", () => { + return { + __esModule: true, + getToken: jest.fn(() => TOKEN) + }; +}) + +beforeEach(() => { + global.fetch = jest.fn().mockResolvedValue({ + async json() { + return {} + } + }); +}) + +describe("mock", () => { + + it("mocks correctly", async () => { + await GetQuestions() + expect(jest.mocked(getToken).mock.calls).toEqual([[]]) + expect(jest.mocked(fetch).mock.calls[0][1]).toEqual({ + "headers": { + "Authorization": `Bearer ${TOKEN}`, + }, + "method": "GET", + }) + }); + +}) \ No newline at end of file diff --git a/apps/frontend/__tests__/unit-tests/question.test.ts b/apps/frontend/__tests__/unit-tests/question.test.ts new file mode 100644 index 0000000000..970407f3ec --- /dev/null +++ b/apps/frontend/__tests__/unit-tests/question.test.ts @@ -0,0 +1,313 @@ +import { CreateQuestion, DeleteQuestion, GetQuestions, GetSingleQuestion, Question } from "@/app/services/question" + +const NEXT_PUBLIC_QUESTION_SERVICE_URL = process.env.NEXT_PUBLIC_QUESTION_SERVICE_URL + +const QUESTIONS = [ + { + "id": 12345, + "docRefId": "asdfDocRef", + "title": "Asdf", + "description": "Asdf description", + "categories": ["Arrays", "Algorithms"], + "complexity": "hard" + }, + { + "id": 12346, + "docRefId": "qwerDocRef", + "title": "Qwer", + "description": "Qwer description", + "categories": ["Strings", "Data Structures"], + "complexity": "medium" + }, + { + "id": 12347, + "docRefId": "zxcvDocRef", + "title": "Zxcv", + "description": "Zxcv description", + "categories": ["Graphs", "Algorithms"], + "complexity": "easy" + }, + { + "id": 12348, + "docRefId": "tyuiDocRef", + "title": "Tyui", + "description": "Tyui description", + "categories": ["Trees", "Recursion"], + "complexity": "hard" + }, + { + "id": 12349, + "docRefId": "ghjkDocRef", + "title": "Ghjk", + "description": "Ghjk description", + "categories": ["Dynamic Programming", "Math"], + "complexity": "medium" + }, + { + "id": 12350, + "docRefId": "bnmlDocRef", + "title": "Bnml", + "description": "Bnml description", + "categories": ["Sorting", "Searching"], + "complexity": "easy" + }, + { + "id": 12351, + "docRefId": "poiuDocRef", + "title": "Poiu", + "description": "Poiu description", + "categories": ["Bit Manipulation", "Algorithms"], + "complexity": "hard" + }, + { + "id": 12352, + "docRefId": "lkjhDocRef", + "title": "Lkjh", + "description": "Lkjh description", + "categories": ["Greedy", "Data Structures"], + "complexity": "medium" + }, + { + "id": 12353, + "docRefId": "mnbvDocRef", + "title": "Mnbv", + "description": "Mnbv description", + "categories": ["Backtracking", "Recursion"], + "complexity": "easy" + }, + { + "id": 12354, + "docRefId": "vcxzDocRef", + "title": "Vcxz", + "description": "Vcxz description", + "categories": ["Graphs", "Dynamic Programming"], + "complexity": "hard" + } +] + +jest.mock("@/app/services/login-store", () => { + return { + __esModule: true, + getToken: jest.fn(() => TOKEN) + }; +}) + +function createMockResponse(obj: any): Response { + // @ts-ignore don't need the whole response + return { + json: () => Promise.resolve(obj), + ok: true, + status: 200 + } +} + +const TOKEN = "mockjwttoken" + +describe("GetQuestions", () => { + beforeEach(() => { + global.fetch = jest.fn().mockResolvedValue({ + async json() { + return QUESTIONS + } + }); + }) + + it("gets all questions on the first page with () call", async () => { + + const res = await GetQuestions() + + expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions`, { + headers: { + "Authorization": `Bearer ${TOKEN}`, + }, + method: "GET", + }]]) + expect(res).toStrictEqual(QUESTIONS) + + }); + + it("formats (page=2) params correctly", async () => { + + const res = await GetQuestions(2) + + expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?offset=10`, { + headers: { + "Authorization": `Bearer ${TOKEN}`, + }, + method: "GET", + }]]) + }); + + it("formats (limit=3) params correctly", async () => { + + await GetQuestions(undefined, 3) + + expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?limit=3`, { + headers: { + "Authorization": `Bearer ${TOKEN}`, + }, + method: "GET", + }]]) + }); + + it("formats (difficulty asc) params correctly", async () => { + + await GetQuestions(undefined, undefined, "difficulty asc") + + expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?sortField=difficulty&sortValue=asc`, { + headers: { + "Authorization": `Bearer ${TOKEN}`, + }, + method: "GET", + }]]) + }); + + it("formats ([\"easy\", \"hard\"]) params correctly", async () => { + + await GetQuestions(undefined, undefined, undefined, ["easy", "hard"]) + + expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?complexity=easy,hard`, { + headers: { + "Authorization": `Bearer ${TOKEN}`, + }, + method: "GET", + }]]) + }); + + it("formats cat params correctly", async () => { + + await GetQuestions(undefined, undefined, undefined, undefined, ["CatA", "CatB"]) + + expect(jest.mocked(fetch).mock.calls).toStrictEqual([[ + `${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?categories=CatA,CatB`, + { + headers: { + "Authorization": `Bearer ${TOKEN}`, + }, + method: "GET", + } + ]]) + }); + + it("formats title params correctly", async () => { + + await GetQuestions(undefined, undefined, undefined, undefined, undefined, "The Title Name") + + expect(jest.mocked(fetch).mock.calls).toStrictEqual([[ + `${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?title=The%20Title%20Name`, + { + headers: { + "Authorization": `Bearer ${TOKEN}`, + }, + method: "GET", + } + ]]) + }); +}) + + +describe("GetSingleQuestion", () => { + const DOCREF = "mockdocref"; + beforeEach(() => { + global.fetch = jest.fn().mockResolvedValue({ + ok: true, // Ensure `ok` is true to hit the success branch + async json() { + return QUESTIONS[0] + }, + text: () => Promise.resolve('mocked response'), + }); + }); + + it("gets a question by docref", async () => { + const res = await GetSingleQuestion(DOCREF); + + expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions/${DOCREF}`, { + headers: { + "Authorization": `Bearer ${TOKEN}`, + }, + method: "GET", + }]]) + expect(res).toStrictEqual(QUESTIONS[0]); + }) +}) + +describe("CreateQuestion", () => { + it("uploads a question", async () => { + // grabs a subset of QUESTIONS[0] + const newQuestion = (({title, description, categories, complexity}) => ({title, description, categories, complexity}))(QUESTIONS[0]) + const createdQuestion = QUESTIONS[0]; + + global.fetch = jest.fn().mockResolvedValue({ + status: 200, + statusText: "OK", + ok: true, // Ensure `ok` is true to hit the success branch + async json() { + return createdQuestion + } + }); + + const res = await CreateQuestion(newQuestion); + + expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions`, { + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${TOKEN}`, + }, + method: "POST", + body: JSON.stringify(newQuestion) + }]]) + expect(res).toStrictEqual(createdQuestion); + }) + + it("fails uploading question", async () => { + // grabs a subset of QUESTIONS[0] + const newQuestion = (({title, description, categories, complexity}) => ({title, description, categories, complexity}))(QUESTIONS[0]) + + global.fetch = jest.fn().mockResolvedValue({ + status: 400, + statusText: "Not Found", + data: "Question title already exists" + }) + + const res = CreateQuestion(newQuestion); + + expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions`, { + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${TOKEN}`, + }, + method: "POST", + body: JSON.stringify(newQuestion) + }]]) + await expect(res).rejects.toThrow("Error creating question: 400 Not Found") + }) + + +}) + + +describe("DeleteQuestion", () => { + const DOCREF = "mockdocref"; + beforeEach(() => { + global.fetch = jest.fn().mockResolvedValue({ + status: 200, + statusText: "OK", + data: `Question with ID ${DOCREF} deleted successfully` + }); + }); + + it("deletes successfully", async () => { + const shouldbeNothing = await DeleteQuestion(DOCREF); + + expect(jest.mocked(fetch).mock.calls).toStrictEqual([[ + `${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions/${DOCREF}`, + { + headers: { + "Authorization": `Bearer ${TOKEN}`, + }, + method: "DELETE", + } + ]]) + expect(shouldbeNothing).toBeUndefined(); + }) +}) diff --git a/apps/frontend/jest.config.ts b/apps/frontend/jest.config.ts new file mode 100644 index 0000000000..689403a2dc --- /dev/null +++ b/apps/frontend/jest.config.ts @@ -0,0 +1,208 @@ +/** + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +import type { Config } from 'jest'; +import nextJest from "next/jest" + +const createJestConfig = nextJest({ + // Provide the path to your Next.js app to load next.config.js and .env files in your test environment + dir: './', +}) + +const config: Config = { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "C:\\Users\\user\\AppData\\Local\\Temp\\jest", + + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + // collectCoverage: false, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + // coverageDirectory: undefined, + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "v8", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // The default configuration for fake timers + // fakeTimers: { + // "enableGlobally": false + // }, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "mjs", + // "cjs", + // "jsx", + // "ts", + // "tsx", + // "json", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: undefined, + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state before every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state and implementation before every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: "jsdom", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + // testMatch: [ + // "**/__tests__/**/*.[jt]s?(x)", + // "**/?(*.)+(spec|test).[tj]s?(x)" + // ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // A map from regular expressions to paths to transformers + // transform: undefined, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "\\\\node_modules\\\\", + // "\\.pnp\\.[^\\\\]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; + +export default createJestConfig(config); + diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 97d014fb96..3f1aaa4c0b 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -6,28 +6,58 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "test": "jest", + "unit-test": "jest --verbose __tests__/unit-tests", + "browser-test": "jest --verbose __tests__/browser-tests" }, "dependencies": { - "@ant-design/cssinjs": "^1.21.1", "@ant-design/icons": "^5.5.1", "@ant-design/nextjs-registry": "^1.0.1", + "@codemirror/commands": "^6.7.1", + "@codemirror/lang-cpp": "^6.0.2", + "@codemirror/lang-go": "^6.0.1", + "@codemirror/lang-java": "^6.0.1", + "@codemirror/lang-javascript": "^6.2.2", + "@codemirror/lang-python": "^6.1.6", + "@codemirror/language": "^6.10.3", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.34.1", "antd": "^5.20.6", + "codemirror": "^6.0.1", + "jwt-decode": "^4.0.0", "next": "14.2.13", + "peerjs": "^1.5.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-timer-hook": "^3.0.7", "react-use-websocket": "^4.9.0", "sass": "^1.79.2", - "typeface-montserrat": "^1.1.13" + "typeface-montserrat": "^1.1.13", + "uuid": "^11.0.3", + "y-codemirror.next": "^0.3.5", + "y-webrtc": "^10.3.0", + "yjs": "^13.6.20" }, "devDependencies": { + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.0.1", + "@types/chromedriver": "^81.0.5", + "@types/codemirror": "^5.60.15", + "@types/jest": "^29.5.14", "@types/node": "^20", + "@types/peerjs": "^1.1.0", "@types/react": "^18.3.8", "@types/react-dom": "^18.3.0", + "@types/selenium-webdriver": "^4.1.27", "eslint": "^8", "eslint-config-next": "14.2.13", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "selenium-webdriver": "^4.26.0", + "ts-node": "^10.9.2", "typescript": "^5" }, "packageManager": "pnpm@9.1.4+sha512.9df9cf27c91715646c7d675d1c9c8e41f6fce88246f1318c1aa6a1ed1aeb3c4f032fcdf4ba63cc69c4fe6d634279176b5358727d8f2cc1e65b65f43ce2f8bfb0" -} +} \ No newline at end of file diff --git a/apps/frontend/pnpm-lock.yaml b/apps/frontend/pnpm-lock.yaml index 7e165fe896..2462485e68 100644 --- a/apps/frontend/pnpm-lock.yaml +++ b/apps/frontend/pnpm-lock.yaml @@ -8,21 +8,54 @@ importers: .: dependencies: - '@ant-design/cssinjs': - specifier: ^1.21.1 - version: 1.21.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@ant-design/icons': specifier: ^5.5.1 version: 5.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@ant-design/nextjs-registry': specifier: ^1.0.1 - version: 1.0.1(@ant-design/cssinjs@1.21.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(antd@5.20.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(next@14.2.13(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.79.2))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 1.0.1(@ant-design/cssinjs@1.21.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(antd@5.20.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(next@14.2.13(@babel/core@7.26.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.79.2))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@codemirror/commands': + specifier: ^6.7.1 + version: 6.7.1 + '@codemirror/lang-cpp': + specifier: ^6.0.2 + version: 6.0.2 + '@codemirror/lang-go': + specifier: ^6.0.1 + version: 6.0.1(@codemirror/view@6.34.1) + '@codemirror/lang-java': + specifier: ^6.0.1 + version: 6.0.1 + '@codemirror/lang-javascript': + specifier: ^6.2.2 + version: 6.2.2 + '@codemirror/lang-python': + specifier: ^6.1.6 + version: 6.1.6(@codemirror/view@6.34.1) + '@codemirror/language': + specifier: ^6.10.3 + version: 6.10.3 + '@codemirror/state': + specifier: ^6.4.1 + version: 6.4.1 + '@codemirror/view': + specifier: ^6.34.1 + version: 6.34.1 antd: specifier: ^5.20.6 version: 5.20.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + codemirror: + specifier: ^6.0.1 + version: 6.0.1(@lezer/common@1.2.3) + jwt-decode: + specifier: ^4.0.0 + version: 4.0.0 next: specifier: 14.2.13 - version: 14.2.13(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.79.2) + version: 14.2.13(@babel/core@7.26.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.79.2) + peerjs: + specifier: ^1.5.4 + version: 1.5.4 react: specifier: ^18.2.0 version: 18.2.0 @@ -41,28 +74,83 @@ importers: typeface-montserrat: specifier: ^1.1.13 version: 1.1.13 + uuid: + specifier: ^11.0.3 + version: 11.0.3 + y-codemirror.next: + specifier: ^0.3.5 + version: 0.3.5(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(yjs@13.6.20) + y-webrtc: + specifier: ^10.3.0 + version: 10.3.0(yjs@13.6.20) + yjs: + specifier: ^13.6.20 + version: 13.6.20 devDependencies: + '@testing-library/dom': + specifier: ^10.4.0 + version: 10.4.0 + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.6.3 + '@testing-library/react': + specifier: ^16.0.1 + version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@types/chromedriver': + specifier: ^81.0.5 + version: 81.0.5 + '@types/codemirror': + specifier: ^5.60.15 + version: 5.60.15 + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 '@types/node': specifier: ^20 version: 20.0.0 + '@types/peerjs': + specifier: ^1.1.0 + version: 1.1.0 '@types/react': specifier: ^18.3.8 version: 18.3.8 '@types/react-dom': specifier: ^18.3.0 version: 18.3.0 + '@types/selenium-webdriver': + specifier: ^4.1.27 + version: 4.1.27 eslint: specifier: ^8 version: 8.0.0 eslint-config-next: specifier: 14.2.13 version: 14.2.13(eslint@8.0.0)(typescript@5.0.2) + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2)) + jest-environment-jsdom: + specifier: ^29.7.0 + version: 29.7.0 + selenium-webdriver: + specifier: ^4.26.0 + version: 4.26.0 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.0.0)(typescript@5.0.2) typescript: specifier: ^5 version: 5.0.2 packages: + '@adobe/css-tools@4.4.0': + resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@ant-design/colors@7.1.0': resolution: {integrity: sha512-MMoDGWn1y9LdQJQSHiCC20x3uZ3CwQnv9QMz6pCmJOrqdgM9YxsoVVY0wtrdXbmfSgnV0KNk6zi09NAhMR2jvg==} @@ -106,10 +194,219 @@ packages: peerDependencies: react: '>=16.9.0' + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.2': + resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.0': + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.26.2': + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.25.9': + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.25.9': + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.0': + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.26.0': + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.25.9': + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.25.9': + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.25.7': resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==} engines: {node: '>=6.9.0'} + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.25.9': + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.0': + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} + engines: {node: '>=6.9.0'} + + '@bazel/runfiles@6.3.1': + resolution: {integrity: sha512-1uLNT5NZsUVIGS4syuHwTzZ8HycMPyr6POA3FCE4GbMtc4rhoJk8aZKtNIRthJYfL+iioppi+rTfH3olMPr9nA==} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@codemirror/autocomplete@6.18.1': + resolution: {integrity: sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA==} + peerDependencies: + '@codemirror/language': ^6.0.0 + '@codemirror/state': ^6.0.0 + '@codemirror/view': ^6.0.0 + '@lezer/common': ^1.0.0 + + '@codemirror/commands@6.7.1': + resolution: {integrity: sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==} + + '@codemirror/lang-cpp@6.0.2': + resolution: {integrity: sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg==} + + '@codemirror/lang-go@6.0.1': + resolution: {integrity: sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==} + + '@codemirror/lang-java@6.0.1': + resolution: {integrity: sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg==} + + '@codemirror/lang-javascript@6.2.2': + resolution: {integrity: sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==} + + '@codemirror/lang-python@6.1.6': + resolution: {integrity: sha512-ai+01WfZhWqM92UqjnvorkxosZ2aq2u28kHvr+N3gu012XqY2CThD67JPMHnGceRfXPDBmn1HnyqowdpF57bNg==} + + '@codemirror/language@6.10.3': + resolution: {integrity: sha512-kDqEU5sCP55Oabl6E7m5N+vZRoc0iWqgDVhEKifcHzPzjqCegcO4amfrYVL9PmPZpl4G0yjkpTpUO/Ui8CzO8A==} + + '@codemirror/lint@6.8.2': + resolution: {integrity: sha512-PDFG5DjHxSEjOXk9TQYYVjZDqlZTFaDBfhQixHnQOEVDDNHUbEh/hstAjcQJaA6FQdZTD1hquXTK0rVBLADR1g==} + + '@codemirror/search@6.5.6': + resolution: {integrity: sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==} + + '@codemirror/state@6.4.1': + resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==} + + '@codemirror/view@6.34.1': + resolution: {integrity: sha512-t1zK/l9UiRqwUNPm+pdIT0qzJlzuVckbTEMVNFhfWkGiBQClstzg+78vedCvLSX0xJEZ6lwZbPpnljL7L6iwMQ==} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@ctrl/tinycolor@3.6.1': resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} engines: {node: '>=10'} @@ -147,6 +444,129 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@lezer/common@1.2.3': + resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} + + '@lezer/cpp@1.1.2': + resolution: {integrity: sha512-macwKtyeUO0EW86r3xWQCzOV9/CF8imJLpJlPv3sDY57cPGeUZ8gXWOWNlJr52TVByMV3PayFQCA5SHEERDmVQ==} + + '@lezer/go@1.0.0': + resolution: {integrity: sha512-co9JfT3QqX1YkrMmourYw2Z8meGC50Ko4d54QEcQbEYpvdUvN4yb0NBZdn/9ertgvjsySxHsKzH3lbm3vqJ4Jw==} + + '@lezer/highlight@1.2.1': + resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} + + '@lezer/java@1.1.3': + resolution: {integrity: sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==} + + '@lezer/javascript@1.4.19': + resolution: {integrity: sha512-j44kbR1QL26l6dMunZ1uhKBFteVGLVCBGNUD2sUaMnic+rbTviVuoK0CD1l9FTW31EueWvFFswCKMH7Z+M3JRA==} + + '@lezer/lr@1.4.2': + resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + + '@lezer/python@1.1.14': + resolution: {integrity: sha512-ykDOb2Ti24n76PJsSa4ZoDF0zH12BSw1LGfQXCYJhJyOGiFTfGaX0Du66Ze72R+u/P35U+O6I9m8TFXov1JzsA==} + + '@msgpack/msgpack@2.8.0': + resolution: {integrity: sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==} + engines: {node: '>= 10'} + '@next/env@14.2.13': resolution: {integrity: sha512-s3lh6K8cbW1h5Nga7NNeXrbe0+2jIIYK9YaA9T7IufDWnZpozdFUp6Hf0d5rNWUKu4fEuSX2rCKlGjCrtylfDw==} @@ -288,18 +708,112 @@ packages: '@rushstack/eslint-patch@1.10.4': resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==} + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + '@testing-library/dom@10.4.0': + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.6.3': + resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.0.1': + resolution: {integrity: sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 + '@types/react-dom': ^18.0.0 + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@tootallnate/once@2.0.0': + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + + '@types/chromedriver@81.0.5': + resolution: {integrity: sha512-VwV+WTTFHYZotBn57QQ8gd4TE7CGJ15KPM+xJJrKbiQQSccTY7zVXuConSBlyWrO+AFpVxuzmluK3xvzxGmkCw==} + + '@types/codemirror@5.60.15': + resolution: {integrity: sha512-dTOvwEQ+ouKJ/rE9LT1Ue2hmP6H1mZv5+CCnNWu2qtiOe2LQa9lCprEY20HxiDmV/Bxh+dXjywmy5aKvoGjULA==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/jsdom@20.0.1': + resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} + '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} '@types/node@20.0.0': resolution: {integrity: sha512-cD2uPTDnQQCVpmRefonO98/PPijuOnnEy5oytWJFPY1N9aJCz2wJ5kSGWO+zJoed2cY2JxQh6yBuUq4vIn61hw==} + '@types/peerjs@1.1.0': + resolution: {integrity: sha512-dVocsfYFg5QQuUB9OAxfrSvz4br4pyX+7M61ZJSRiYtE3NdayShk1p1Y8b9TmCj724TwHskVraeF7wyPl0rYcg==} + deprecated: This is a stub types definition. peerjs provides its own type definitions, so you do not need this installed. + '@types/prop-types@15.7.13': resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} @@ -309,6 +823,27 @@ packages: '@types/react@18.3.8': resolution: {integrity: sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==} + '@types/selenium-webdriver@4.1.27': + resolution: {integrity: sha512-ALqsj8D7Swb6MnBQuAQ58J3KC3yh6fLGtAmpBmnZX8j+0kmP7NaLt56CuzBw2W2bXPrvHFTgn8iekOQFUKXEQA==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/tern@0.23.9': + resolution: {integrity: sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@types/ws@8.5.13': + resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + '@typescript-eslint/eslint-plugin@8.8.0': resolution: {integrity: sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -366,16 +901,31 @@ packages: resolution: {integrity: sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + abab@2.0.6: + resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + deprecated: Use your platform's native atob() and btoa() methods instead + + acorn-globals@7.0.1: + resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + acorn@8.12.1: resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -383,6 +933,10 @@ packages: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -395,6 +949,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -405,12 +963,25 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} aria-query@5.1.3: resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-buffer-byte-length@1.0.1: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} @@ -449,6 +1020,9 @@ packages: ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -461,9 +1035,37 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-preset-current-node-syntax@1.1.0: + resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -474,6 +1076,20 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.24.2: + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -486,23 +1102,63 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + caniuse-lite@1.0.30001666: resolution: {integrity: sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==} + caniuse-lite@1.0.30001677: + resolution: {integrity: sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==} + + chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + chokidar@4.0.1: resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==} engines: {node: '>= 14.16.0'} + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cjs-module-lexer@1.4.1: + resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} + classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + codemirror@6.0.1: + resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} + + collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -510,25 +1166,63 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + compute-scroll-into-view@3.1.0: resolution: {integrity: sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==} concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + copy-to-clipboard@3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssom@0.3.8: + resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} + + cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + + cssstyle@2.3.0: + resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} + engines: {node: '>=8'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + data-urls@3.0.2: + resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} + engines: {node: '>=12'} + data-view-buffer@1.0.1: resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} @@ -561,6 +1255,17 @@ packages: supports-color: optional: true + decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + + dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} @@ -568,6 +1273,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -576,6 +1285,26 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -584,8 +1313,26 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + domexception@4.0.0: + resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} + engines: {node: '>=12'} + deprecated: Use your platform's native DOMException instead + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.50: + resolution: {integrity: sha512-eMVObiUQ2LdgeO1F/ySTXsvqvxb6ZH2zPGaMYsWzRDdOddUa77tdmI0ltg+L16UpbWdhPmuF3wIQYyQq65WfZw==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -601,6 +1348,16 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + err-code@3.0.1: + resolution: {integrity: sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + es-abstract@1.23.3: resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} engines: {node: '>= 0.4'} @@ -635,10 +1392,23 @@ packages: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + eslint-config-next@14.2.13: resolution: {integrity: sha512-aro1EKAoyYchnO/3Tlo91hnNBO7QO7qnv/79MAFC+4Jq8TdUVKQlht5d2F+YjrePjdpOvfL+mV9JPfyYNwkk1g==} peerDependencies: @@ -740,6 +1510,11 @@ packages: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} @@ -756,6 +1531,21 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -772,6 +1562,9 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -780,6 +1573,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} @@ -794,9 +1591,18 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -810,10 +1616,29 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-browser-rtc@1.1.0: + resolution: {integrity: sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + get-symbol-description@1.0.2: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} @@ -838,6 +1663,10 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} @@ -881,6 +1710,32 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + html-encoding-sniffer@3.0.0: + resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} + engines: {node: '>=12'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@4.0.6: resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} engines: {node: '>= 4'} @@ -889,6 +1744,9 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + immutable@4.3.7: resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} @@ -896,10 +1754,19 @@ packages: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -919,6 +1786,9 @@ packages: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-async-function@2.0.0: resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} engines: {node: '>= 0.4'} @@ -960,6 +1830,10 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + is-generator-function@1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} @@ -984,6 +1858,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -996,6 +1873,10 @@ packages: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} engines: {node: '>= 0.4'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -1019,12 +1900,42 @@ packages: resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} engines: {node: '>= 0.4'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isomorphic.js@0.2.5: + resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + iterator.prototype@1.1.2: resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} @@ -1032,16 +1943,175 @@ packages: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-jsdom@29.7.0: + resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsdom@20.0.3: + resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} + engines: {node: '>=14'} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -1055,13 +2125,29 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -1069,13 +2155,35 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lib0@0.2.98: + resolution: {integrity: sha512-XteTiNO0qEXqqweWx+b21p/fBnNHUA1NwAtJNJek1oPrewEZs2uiT4gWivHKr9GqCjDPAhchz0UQO8NwU3bBNA==} + engines: {node: '>=16'} + hasBin: true + + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1083,6 +2191,26 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1091,6 +2219,22 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1134,6 +2278,23 @@ packages: sass: optional: true + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nwsapi@2.2.13: + resolution: {integrity: sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1173,14 +2334,48 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -1196,6 +2391,14 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + peerjs-js-binarypack@2.1.0: + resolution: {integrity: sha512-YIwCC+pTzp3Bi8jPI9UFKO0t0SLo6xALnHkiNt/iUFmUUZG0fEEmEyFKvjsDKweiFitzHRyhuh6NvyJZ4nNxMg==} + engines: {node: '>= 14.0.0'} + + peerjs@1.5.4: + resolution: {integrity: sha512-yFsoLMnurJKlQbx6kVSBpOp+AlNldY1JQS2BrSsHLKCZnq6t7saHleuHM5svuLNbQkUJXHLF3sKOJB1K0xulOw==} + engines: {node: '>= 14'} + picocolors@1.1.0: resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} @@ -1203,6 +2406,14 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -1215,20 +2426,47 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + rc-cascader@3.28.1: resolution: {integrity: sha512-9+8oHIMWVLHxuaapDiqFNmD9KSyKN/P4bo9x/MBuDbyTqP8f2/POmmZxdXWBO3yq/uE3pKyQCXYNUxrNfHRv2A==} peerDependencies: @@ -1465,6 +2703,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -1480,10 +2721,21 @@ packages: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + readdirp@4.0.1: resolution: {integrity: sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==} engines: {node: '>= 14.16.0'} + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + reflect.getprototypeof@1.0.6: resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} engines: {node: '>= 0.4'} @@ -1499,16 +2751,35 @@ packages: resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} engines: {node: '>=8'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} + engines: {node: '>=10'} + resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -1533,21 +2804,41 @@ packages: resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} engines: {node: '>=0.4'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-regex-test@1.0.3: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sass@1.79.2: resolution: {integrity: sha512-YmT1aoF1MwHsZEu/eXhbAJNsPGAhNP4UixW9ckEwWCvPcVdVF0/C104OGDVEqtoctKq0N+wM20O/rj+sSPsWeg==} engines: {node: '>=14.0.0'} hasBin: true + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} scroll-into-view-if-needed@3.1.0: resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + sdp@3.2.0: + resolution: {integrity: sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==} + + selenium-webdriver@4.26.0: + resolution: {integrity: sha512-nA7jMRIPV17mJmAiTDBWN96Sy0Uxrz5CCLb7bLVV6PpL417SyBMPc2Zo/uoREc2EOHlzHwHwAlFtgmSngSY4WQ==} + engines: {node: '>= 14.21.0'} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -1565,6 +2856,9 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1577,14 +2871,41 @@ packages: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-peer@9.11.1: + resolution: {integrity: sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} engines: {node: '>= 0.4'} @@ -1596,6 +2917,10 @@ packages: string-convert@0.2.1: resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -1625,6 +2950,12 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1637,10 +2968,25 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + style-mod@4.1.2: + resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} + styled-jsx@5.1.1: resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -1661,21 +3007,39 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - text-table@0.2.0: + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} throttle-debounce@5.0.2: resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} engines: {node: '>=12.22'} + tmp@0.2.3: + resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + engines: {node: '>=14.14'} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1683,12 +3047,34 @@ packages: toggle-selection@1.0.6: resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tr46@3.0.0: + resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} + engines: {node: '>=12'} + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} peerDependencies: typescript: '>=4.2.0' + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -1699,10 +3085,18 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + typed-array-buffer@1.0.2: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} @@ -1730,12 +3124,69 @@ packages: unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@11.0.3: + resolution: {integrity: sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + v8-compile-cache@2.4.0: resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + + w3c-xmlserializer@4.0.0: + resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} + engines: {node: '>=14'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + webrtc-adapter@9.0.1: + resolution: {integrity: sha512-1AQO+d4ElfVSXyzNVTOewgGT/tAomwwztX/6e3totvyyzXPvXIIuUUjAmyZGbKBKbZOXauuJooZm3g6IuFuiNQ==} + engines: {node: '>=6.0.0', npm: '>=3.10.0'} + + whatwg-encoding@2.0.0: + resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} + engines: {node: '>=12'} + + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + + whatwg-url@11.0.0: + resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} + engines: {node: '>=12'} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -1771,8 +3222,85 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + y-codemirror.next@0.3.5: + resolution: {integrity: sha512-VluNu3e5HfEXybnypnsGwKAj+fKLd4iAnR7JuX1Sfyydmn1jCBS5wwEL/uS04Ch2ib0DnMAOF6ZRR/8kK3wyGw==} + peerDependencies: + '@codemirror/state': ^6.0.0 + '@codemirror/view': ^6.0.0 + yjs: ^13.5.6 + + y-protocols@1.0.6: + resolution: {integrity: sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + yjs: ^13.0.0 + + y-webrtc@10.3.0: + resolution: {integrity: sha512-KalJr7dCgUgyVFxoG3CQYbpS0O2qybegD0vI4bYnYHI0MOwoVbucED3RZ5f2o1a5HZb1qEssUKS0H/Upc6p1lA==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + yjs: ^13.6.8 + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yjs@13.6.20: + resolution: {integrity: sha512-Z2YZI+SYqK7XdWlloI3lhMiKnCdFCVC4PchpdO+mCYwtiTwncjUbnRK9R1JmkNfdmHyDXuWN3ibJAt0wsqTbLQ==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + snapshots: + '@adobe/css-tools@4.4.0': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + '@ant-design/colors@7.1.0': dependencies: '@ctrl/tinycolor': 3.6.1 @@ -1813,11 +3341,11 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@ant-design/nextjs-registry@1.0.1(@ant-design/cssinjs@1.21.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(antd@5.20.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(next@14.2.13(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.79.2))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@ant-design/nextjs-registry@1.0.1(@ant-design/cssinjs@1.21.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(antd@5.20.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(next@14.2.13(@babel/core@7.26.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.79.2))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@ant-design/cssinjs': 1.21.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) antd: 5.20.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - next: 14.2.13(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.79.2) + next: 14.2.13(@babel/core@7.26.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.79.2) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -1830,10 +3358,286 @@ snapshots: resize-observer-polyfill: 1.5.1 throttle-debounce: 5.0.2 + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.0 + + '@babel/compat-data@7.26.2': {} + + '@babel/core@7.26.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + convert-source-map: 2.0.0 + debug: 4.3.7 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.26.2': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + + '@babel/helper-compilation-targets@7.25.9': + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.25.9': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.26.0': + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + + '@babel/parser@7.26.2': + dependencies: + '@babel/types': 7.26.0 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/runtime@7.25.7': dependencies: regenerator-runtime: 0.14.1 + '@babel/template@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + + '@babel/traverse@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + debug: 4.3.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.26.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@bazel/runfiles@6.3.1': {} + + '@bcoe/v8-coverage@0.2.3': {} + + '@codemirror/autocomplete@6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3)': + dependencies: + '@codemirror/language': 6.10.3 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.34.1 + '@lezer/common': 1.2.3 + + '@codemirror/commands@6.7.1': + dependencies: + '@codemirror/language': 6.10.3 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.34.1 + '@lezer/common': 1.2.3 + + '@codemirror/lang-cpp@6.0.2': + dependencies: + '@codemirror/language': 6.10.3 + '@lezer/cpp': 1.1.2 + + '@codemirror/lang-go@6.0.1(@codemirror/view@6.34.1)': + dependencies: + '@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3) + '@codemirror/language': 6.10.3 + '@codemirror/state': 6.4.1 + '@lezer/common': 1.2.3 + '@lezer/go': 1.0.0 + transitivePeerDependencies: + - '@codemirror/view' + + '@codemirror/lang-java@6.0.1': + dependencies: + '@codemirror/language': 6.10.3 + '@lezer/java': 1.1.3 + + '@codemirror/lang-javascript@6.2.2': + dependencies: + '@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3) + '@codemirror/language': 6.10.3 + '@codemirror/lint': 6.8.2 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.34.1 + '@lezer/common': 1.2.3 + '@lezer/javascript': 1.4.19 + + '@codemirror/lang-python@6.1.6(@codemirror/view@6.34.1)': + dependencies: + '@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3) + '@codemirror/language': 6.10.3 + '@codemirror/state': 6.4.1 + '@lezer/common': 1.2.3 + '@lezer/python': 1.1.14 + transitivePeerDependencies: + - '@codemirror/view' + + '@codemirror/language@6.10.3': + dependencies: + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.34.1 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + style-mod: 4.1.2 + + '@codemirror/lint@6.8.2': + dependencies: + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.34.1 + crelt: 1.0.6 + + '@codemirror/search@6.5.6': + dependencies: + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.34.1 + crelt: 1.0.6 + + '@codemirror/state@6.4.1': {} + + '@codemirror/view@6.34.1': + dependencies: + '@codemirror/state': 6.4.1 + style-mod: 4.1.2 + w3c-keyname: 2.2.8 + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + '@ctrl/tinycolor@3.6.1': {} '@emotion/hash@0.8.0': {} @@ -1880,6 +3684,242 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.0.0 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.0.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.0.0 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.0.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@29.7.0': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 20.0.0 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.26.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.6 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.0.0 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@lezer/common@1.2.3': {} + + '@lezer/cpp@1.1.2': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/go@1.0.0': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/highlight@1.2.1': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/java@1.1.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/javascript@1.4.19': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/lr@1.4.2': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/python@1.1.14': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@msgpack/msgpack@2.8.0': {} + '@next/env@14.2.13': {} '@next/eslint-plugin-next@14.2.13': @@ -2003,6 +4043,16 @@ snapshots: '@rushstack/eslint-patch@1.10.4': {} + '@sinclair/typebox@0.27.8': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.5': @@ -2010,10 +4060,113 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.7.0 + '@testing-library/dom@10.4.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/runtime': 7.25.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.6.3': + dependencies: + '@adobe/css-tools': 4.4.0 + aria-query: 5.1.3 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + lodash: 4.17.21 + redent: 3.0.0 + + '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@babel/runtime': 7.25.7 + '@testing-library/dom': 10.4.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.8 + '@types/react-dom': 18.3.0 + + '@tootallnate/once@2.0.0': {} + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.26.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.26.0 + + '@types/chromedriver@81.0.5': + dependencies: + '@types/node': 20.0.0 + + '@types/codemirror@5.60.15': + dependencies: + '@types/tern': 0.23.9 + + '@types/estree@1.0.6': {} + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 20.0.0 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/jsdom@20.0.1': + dependencies: + '@types/node': 20.0.0 + '@types/tough-cookie': 4.0.5 + parse5: 7.2.1 + '@types/json5@0.0.29': {} '@types/node@20.0.0': {} + '@types/peerjs@1.1.0': + dependencies: + peerjs: 1.5.4 + '@types/prop-types@15.7.13': {} '@types/react-dom@18.3.0': @@ -2025,6 +4178,29 @@ snapshots: '@types/prop-types': 15.7.13 csstype: 3.1.3 + '@types/selenium-webdriver@4.1.27': + dependencies: + '@types/node': 20.0.0 + '@types/ws': 8.5.13 + + '@types/stack-utils@2.0.3': {} + + '@types/tern@0.23.9': + dependencies: + '@types/estree': 1.0.6 + + '@types/tough-cookie@4.0.5': {} + + '@types/ws@8.5.13': + dependencies: + '@types/node': 20.0.0 + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + '@typescript-eslint/eslint-plugin@8.8.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint@8.0.0)(typescript@5.0.2)': dependencies: '@eslint-community/regexpp': 4.11.1 @@ -2106,12 +4282,29 @@ snapshots: '@typescript-eslint/types': 8.8.0 eslint-visitor-keys: 3.4.3 + abab@2.0.6: {} + + acorn-globals@7.0.1: + dependencies: + acorn: 8.12.1 + acorn-walk: 8.3.4 + acorn-jsx@5.3.2(acorn@8.12.1): dependencies: acorn: 8.12.1 + acorn-walk@8.3.4: + dependencies: + acorn: 8.12.1 + acorn@8.12.1: {} + agent-base@6.0.2: + dependencies: + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -2121,6 +4314,10 @@ snapshots: ansi-colors@4.1.3: {} + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -2129,6 +4326,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.1: {} antd@5.20.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0): @@ -2189,12 +4388,27 @@ snapshots: - luxon - moment + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@4.1.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} aria-query@5.1.3: dependencies: deep-equal: 2.2.3 + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + array-buffer-byte-length@1.0.1: dependencies: call-bind: 1.0.7 @@ -2264,6 +4478,8 @@ snapshots: ast-types-flow@0.0.8: {} + asynckit@0.4.0: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 @@ -2272,8 +4488,65 @@ snapshots: axobject-query@4.1.0: {} + babel-jest@29.7.0(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.26.0) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.25.9 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.6 + + babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.0) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) + + babel-preset-jest@29.6.3(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + balanced-match@1.0.2: {} + base64-js@1.5.1: {} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -2287,6 +4560,24 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.24.2: + dependencies: + caniuse-lite: 1.0.30001677 + electron-to-chromium: 1.5.50 + node-releases: 2.0.18 + update-browserslist-db: 1.1.1(browserslist@4.24.2) + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -2301,45 +4592,127 @@ snapshots: callsites@3.1.0: {} + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + caniuse-lite@1.0.30001666: {} + caniuse-lite@1.0.30001677: {} + + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + char-regex@1.0.2: {} + chokidar@4.0.1: dependencies: readdirp: 4.0.1 + ci-info@3.9.0: {} + + cjs-module-lexer@1.4.1: {} + classnames@2.5.1: {} client-only@0.0.1: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + co@4.6.0: {} + + codemirror@6.0.1(@lezer/common@1.2.3): + dependencies: + '@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3) + '@codemirror/commands': 6.7.1 + '@codemirror/language': 6.10.3 + '@codemirror/lint': 6.8.2 + '@codemirror/search': 6.5.6 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.34.1 + transitivePeerDependencies: + - '@lezer/common' + + collect-v8-coverage@1.0.2: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + compute-scroll-into-view@3.1.0: {} concat-map@0.0.1: {} + convert-source-map@2.0.0: {} + copy-to-clipboard@3.3.3: dependencies: toggle-selection: 1.0.6 + core-util-is@1.0.3: {} + + create-jest@29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + create-require@1.1.1: {} + + crelt@1.0.6: {} + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + css.escape@1.5.1: {} + + cssom@0.3.8: {} + + cssom@0.5.0: {} + + cssstyle@2.3.0: + dependencies: + cssom: 0.3.8 + csstype@3.1.3: {} damerau-levenshtein@1.0.8: {} + data-urls@3.0.2: + dependencies: + abab: 2.0.6 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + data-view-buffer@1.0.1: dependencies: call-bind: 1.0.7 @@ -2368,6 +4741,10 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js@10.4.3: {} + + dedent@1.5.3: {} + deep-equal@2.2.3: dependencies: array-buffer-byte-length: 1.0.1 @@ -2391,6 +4768,8 @@ snapshots: deep-is@0.1.4: {} + deepmerge@4.3.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.0 @@ -2403,6 +4782,16 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-newline@3.1.0: {} + + diff-sequences@29.6.3: {} + + diff@4.0.2: {} + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -2411,8 +4800,20 @@ snapshots: dependencies: esutils: 2.0.3 + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + domexception@4.0.0: + dependencies: + webidl-conversions: 7.0.0 + eastasianwidth@0.2.0: {} + electron-to-chromium@1.5.50: {} + + emittery@0.13.1: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -2427,6 +4828,14 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + entities@4.5.0: {} + + err-code@3.0.1: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + es-abstract@1.23.3: dependencies: array-buffer-byte-length: 1.0.1 @@ -2531,8 +4940,20 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 + escalade@3.2.0: {} + + escape-string-regexp@2.0.0: {} + escape-string-regexp@4.0.0: {} + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + eslint-config-next@14.2.13(eslint@8.0.0)(typescript@5.0.2): dependencies: '@next/eslint-plugin-next': 14.2.13 @@ -2541,8 +4962,8 @@ snapshots: '@typescript-eslint/parser': 8.8.0(eslint@8.0.0)(typescript@5.0.2) eslint: 8.0.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.0.0) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.0.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint@8.0.0))(eslint@8.0.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint@8.0.0))(eslint@8.0.0))(eslint@8.0.0) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.0.0) eslint-plugin-react: 7.37.1(eslint@8.0.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.0.0) @@ -2561,37 +4982,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.0.0): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint@8.0.0))(eslint@8.0.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.0.0 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.0.0))(eslint@8.0.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.0.0) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.0.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint@8.0.0))(eslint@8.0.0))(eslint@8.0.0) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.0.0))(eslint@8.0.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.0.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.8.0(eslint@8.0.0)(typescript@5.0.2) eslint: 8.0.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.0.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint@8.0.0))(eslint@8.0.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.0.0): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint@8.0.0))(eslint@8.0.0))(eslint@8.0.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -2602,7 +5023,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.0.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.0.0))(eslint@8.0.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.0(eslint@8.0.0)(typescript@5.0.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.0.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -2728,6 +5149,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.12.1) eslint-visitor-keys: 3.4.3 + esprima@4.0.1: {} + esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -2740,6 +5163,30 @@ snapshots: esutils@2.0.3: {} + eventemitter3@4.0.7: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit@0.1.2: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + fast-deep-equal@3.1.3: {} fast-glob@3.3.2: @@ -2758,6 +5205,10 @@ snapshots: dependencies: reusify: 1.0.4 + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -2766,6 +5217,11 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + flat-cache@3.2.0: dependencies: flatted: 3.3.1 @@ -2783,8 +5239,17 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + fs.realpath@1.0.0: {} + fsevents@2.3.3: + optional: true + function-bind@1.1.2: {} function.prototype.name@1.1.6: @@ -2798,6 +5263,12 @@ snapshots: functions-have-names@1.2.3: {} + gensync@1.0.0-beta.2: {} + + get-browser-rtc@1.1.0: {} + + get-caller-file@2.0.5: {} + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -2806,6 +5277,10 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.2 + get-package-type@0.1.0: {} + + get-stream@6.0.1: {} + get-symbol-description@1.0.2: dependencies: call-bind: 1.0.7 @@ -2841,6 +5316,8 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + globals@11.12.0: {} + globals@13.24.0: dependencies: type-fest: 0.20.2 @@ -2878,10 +5355,41 @@ snapshots: dependencies: function-bind: 1.1.2 + html-encoding-sniffer@3.0.0: + dependencies: + whatwg-encoding: 2.0.0 + + html-escaper@2.0.2: {} + + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + ignore@4.0.6: {} ignore@5.3.2: {} + immediate@3.0.6: {} + immutable@4.3.7: {} import-fresh@3.3.0: @@ -2889,8 +5397,15 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -2914,6 +5429,8 @@ snapshots: call-bind: 1.0.7 get-intrinsic: 1.2.4 + is-arrayish@0.2.1: {} + is-async-function@2.0.0: dependencies: has-tostringtag: 1.0.2 @@ -2953,6 +5470,8 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-generator-fn@2.1.0: {} + is-generator-function@1.0.10: dependencies: has-tostringtag: 1.0.2 @@ -2971,6 +5490,8 @@ snapshots: is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: {} + is-regex@1.1.4: dependencies: call-bind: 1.0.7 @@ -2982,55 +5503,468 @@ snapshots: dependencies: call-bind: 1.0.7 - is-string@1.0.7: + is-stream@2.0.1: {} + + is-string@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-symbol@1.0.4: + dependencies: + has-symbols: 1.0.3 + + is-typed-array@1.1.13: + dependencies: + which-typed-array: 1.1.15 + + is-weakmap@2.0.2: {} + + is-weakref@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-weakset@2.0.3: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isomorphic.js@0.2.5: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.26.0 + '@babel/parser': 7.26.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.26.0 + '@babel/parser': 7.26.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.3.7 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + iterator.prototype@1.1.2: + dependencies: + define-properties: 1.2.1 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + reflect.getprototypeof: 1.0.6 + set-function-name: 2.0.2 + + jackspeak@2.3.6: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.0.0 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.3 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2)): + dependencies: + '@babel/core': 7.26.0 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.0) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.0.0 + ts-node: 10.9.2(@types/node@20.0.0)(typescript@5.0.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-jsdom@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/jsdom': 20.0.1 + '@types/node': 20.0.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + jsdom: 20.0.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.0.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 20.0.0 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.26.2 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.0.0 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: dependencies: - has-tostringtag: 1.0.2 + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.8 + resolve.exports: 2.0.2 + slash: 3.0.0 - is-symbol@1.0.4: + jest-runner@29.7.0: dependencies: - has-symbols: 1.0.3 + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.0.0 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color - is-typed-array@1.1.13: + jest-runtime@29.7.0: dependencies: - which-typed-array: 1.1.15 + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.0.0 + chalk: 4.1.2 + cjs-module-lexer: 1.4.1 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color - is-weakmap@2.0.2: {} + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.26.0 + '@babel/generator': 7.26.2 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) + '@babel/types': 7.26.0 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color - is-weakref@1.0.2: + jest-util@29.7.0: dependencies: - call-bind: 1.0.7 + '@jest/types': 29.6.3 + '@types/node': 20.0.0 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 - is-weakset@2.0.3: + jest-validate@29.7.0: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - - isarray@2.0.5: {} + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 - isexe@2.0.0: {} + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.0.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 - iterator.prototype@1.1.2: + jest-worker@29.7.0: dependencies: - define-properties: 1.2.1 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - reflect.getprototypeof: 1.0.6 - set-function-name: 2.0.2 + '@types/node': 20.0.0 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 - jackspeak@2.3.6: + jest@29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2)): dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node js-tokens@4.0.0: {} + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + js-yaml@4.1.0: dependencies: argparse: 2.0.1 + jsdom@20.0.3: + dependencies: + abab: 2.0.6 + acorn: 8.12.1 + acorn-globals: 7.0.1 + cssom: 0.5.0 + cssstyle: 2.3.0 + data-urls: 3.0.2 + decimal.js: 10.4.3 + domexception: 4.0.0 + escodegen: 2.1.0 + form-data: 4.0.1 + html-encoding-sniffer: 3.0.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.13 + parse5: 7.2.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 4.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 2.0.0 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + ws: 8.18.0 + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@3.0.2: {} + json-buffer@3.0.1: {} + json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -3043,6 +5977,8 @@ snapshots: dependencies: minimist: 1.2.8 + json5@2.2.3: {} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -3050,29 +5986,76 @@ snapshots: object.assign: 4.1.5 object.values: 1.2.0 + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + + jwt-decode@4.0.0: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + kleur@3.0.3: {} + language-subtag-registry@0.3.23: {} language-tags@1.0.9: dependencies: language-subtag-registry: 0.3.23 + leven@3.1.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + lib0@0.2.98: + dependencies: + isomorphic.js: 0.2.5 + + lie@3.3.0: + dependencies: + immediate: 3.0.6 + + lines-and-columns@1.2.4: {} + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + lodash.merge@4.6.2: {} + lodash@4.17.21: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 lru-cache@10.4.3: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lz-string@1.5.0: {} + + make-dir@4.0.0: + dependencies: + semver: 7.6.3 + + make-error@1.3.6: {} + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + merge-stream@2.0.0: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -3080,6 +6063,16 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + min-indent@1.0.1: {} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -3098,7 +6091,7 @@ snapshots: natural-compare@1.4.0: {} - next@14.2.13(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.79.2): + next@14.2.13(@babel/core@7.26.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.79.2): dependencies: '@next/env': 14.2.13 '@swc/helpers': 0.5.5 @@ -3108,7 +6101,7 @@ snapshots: postcss: 8.4.31 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - styled-jsx: 5.1.1(react@18.2.0) + styled-jsx: 5.1.1(@babel/core@7.26.0)(react@18.2.0) optionalDependencies: '@next/swc-darwin-arm64': 14.2.13 '@next/swc-darwin-x64': 14.2.13 @@ -3124,6 +6117,18 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-int64@0.4.0: {} + + node-releases@2.0.18: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nwsapi@2.2.13: {} + object-assign@4.1.1: {} object-inspect@1.13.2: {} @@ -3171,6 +6176,10 @@ snapshots: dependencies: wrappy: 1.0.2 + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3180,10 +6189,39 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-try@2.2.0: {} + + pako@1.0.11: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.26.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse5@7.2.1: + dependencies: + entities: 4.5.0 + + path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -3195,10 +6233,25 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + peerjs-js-binarypack@2.1.0: {} + + peerjs@1.5.4: + dependencies: + '@msgpack/msgpack': 2.8.0 + eventemitter3: 4.0.7 + peerjs-js-binarypack: 2.1.0 + webrtc-adapter: 9.0.1 + picocolors@1.1.0: {} picomatch@2.3.1: {} + pirates@4.0.6: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + possible-typed-array-names@1.0.0: {} postcss@8.4.31: @@ -3209,18 +6262,47 @@ snapshots: prelude-ls@1.2.1: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + process-nextick-args@2.0.1: {} + progress@2.0.3: {} + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 + psl@1.9.0: {} + punycode@2.3.1: {} + pure-rand@6.1.0: {} + + querystringify@2.2.0: {} + queue-microtask@1.2.3: {} + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + rc-cascader@3.28.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@babel/runtime': 7.25.7 @@ -3548,6 +6630,8 @@ snapshots: react-is@16.13.1: {} + react-is@17.0.2: {} + react-is@18.3.1: {} react-timer-hook@3.0.7(react@18.2.0): @@ -3560,8 +6644,29 @@ snapshots: dependencies: loose-envify: 1.4.0 + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + readdirp@4.0.1: {} + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + reflect.getprototypeof@1.0.6: dependencies: call-bind: 1.0.7 @@ -3583,12 +6688,24 @@ snapshots: regexpp@3.2.0: {} + require-directory@2.1.1: {} + + requires-port@1.0.0: {} + resize-observer-polyfill@1.5.1: {} + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + resolve-from@4.0.0: {} + resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve.exports@2.0.2: {} + resolve@1.22.8: dependencies: is-core-module: 2.15.1 @@ -3618,18 +6735,28 @@ snapshots: has-symbols: 1.0.3 isarray: 2.0.5 + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + safe-regex-test@1.0.3: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-regex: 1.1.4 + safer-buffer@2.1.2: {} + sass@1.79.2: dependencies: chokidar: 4.0.1 immutable: 4.3.7 source-map-js: 1.2.1 + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -3638,6 +6765,18 @@ snapshots: dependencies: compute-scroll-into-view: 3.1.0 + sdp@3.2.0: {} + + selenium-webdriver@4.26.0: + dependencies: + '@bazel/runfiles': 6.3.1 + jszip: 3.10.1 + tmp: 0.2.3 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + semver@6.3.1: {} semver@7.6.3: {} @@ -3658,6 +6797,8 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + setimmediate@1.0.5: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3671,10 +6812,41 @@ snapshots: get-intrinsic: 1.2.4 object-inspect: 1.13.2 + signal-exit@3.0.7: {} + signal-exit@4.1.0: {} + simple-peer@9.11.1: + dependencies: + buffer: 6.0.3 + debug: 4.3.7 + err-code: 3.0.1 + get-browser-rtc: 1.1.0 + queue-microtask: 1.2.3 + randombytes: 2.1.0 + readable-stream: 3.6.2 + transitivePeerDependencies: + - supports-color + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + source-map-js@1.2.1: {} + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + stop-iteration-iterator@1.0.0: dependencies: internal-slot: 1.0.7 @@ -3683,6 +6855,11 @@ snapshots: string-convert@0.2.1: {} + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -3739,6 +6916,14 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -3749,12 +6934,24 @@ snapshots: strip-bom@3.0.0: {} + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-json-comments@3.1.1: {} - styled-jsx@5.1.1(react@18.2.0): + style-mod@4.1.2: {} + + styled-jsx@5.1.1(@babel/core@7.26.0)(react@18.2.0): dependencies: client-only: 0.0.1 react: 18.2.0 + optionalDependencies: + '@babel/core': 7.26.0 stylis@4.3.4: {} @@ -3762,24 +6959,69 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} + symbol-tree@3.2.4: {} + tapable@2.2.1: {} + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + text-table@0.2.0: {} throttle-debounce@5.0.2: {} + tmp@0.2.3: {} + + tmpl@1.0.5: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 toggle-selection@1.0.6: {} + tough-cookie@4.1.4: + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tr46@3.0.0: + dependencies: + punycode: 2.3.1 + ts-api-utils@1.3.0(typescript@5.0.2): dependencies: typescript: 5.0.2 + ts-node@10.9.2(@types/node@20.0.0)(typescript@5.0.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.0.0 + acorn: 8.12.1 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.0.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -3793,8 +7035,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-detect@4.0.8: {} + type-fest@0.20.2: {} + type-fest@0.21.3: {} + typed-array-buffer@1.0.2: dependencies: call-bind: 1.0.7 @@ -3838,12 +7084,64 @@ snapshots: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + universalify@0.2.0: {} + + update-browserslist-db@1.1.1(browserslist@4.24.2): + dependencies: + browserslist: 4.24.2 + escalade: 3.2.0 + picocolors: 1.1.0 + uri-js@4.4.1: dependencies: punycode: 2.3.1 + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + util-deprecate@1.0.2: {} + + uuid@11.0.3: {} + + v8-compile-cache-lib@3.0.1: {} + v8-compile-cache@2.4.0: {} + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + w3c-keyname@2.2.8: {} + + w3c-xmlserializer@4.0.0: + dependencies: + xml-name-validator: 4.0.0 + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + webidl-conversions@7.0.0: {} + + webrtc-adapter@9.0.1: + dependencies: + sdp: 3.2.0 + + whatwg-encoding@2.0.0: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@3.0.0: {} + + whatwg-url@11.0.0: + dependencies: + tr46: 3.0.0 + webidl-conversions: 7.0.0 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 @@ -3901,3 +7199,63 @@ snapshots: strip-ansi: 7.1.0 wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + ws@8.18.0: {} + + xml-name-validator@4.0.0: {} + + xmlchars@2.2.0: {} + + y-codemirror.next@0.3.5(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(yjs@13.6.20): + dependencies: + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.34.1 + lib0: 0.2.98 + yjs: 13.6.20 + + y-protocols@1.0.6(yjs@13.6.20): + dependencies: + lib0: 0.2.98 + yjs: 13.6.20 + + y-webrtc@10.3.0(yjs@13.6.20): + dependencies: + lib0: 0.2.98 + simple-peer: 9.11.1 + y-protocols: 1.0.6(yjs@13.6.20) + yjs: 13.6.20 + optionalDependencies: + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yjs@13.6.20: + dependencies: + lib0: 0.2.98 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/apps/frontend/src/app/collaboration/[id]/page.tsx b/apps/frontend/src/app/collaboration/[id]/page.tsx new file mode 100644 index 0000000000..f538285987 --- /dev/null +++ b/apps/frontend/src/app/collaboration/[id]/page.tsx @@ -0,0 +1,657 @@ +"use client"; +import Header from "@/components/Header/header"; +import { + Button, + Col, + Input, + Layout, + Modal, + message, + Row, + TabsProps, + Tag, + Typography, + Spin, + Tooltip, +} from "antd"; +import { Content } from "antd/es/layout/layout"; +import "./styles.scss"; +import { useRouter } from "next/navigation"; +import { useEffect, useRef, useState } from "react"; +import { GetSingleQuestion, Question } from "@/app/services/question"; +import { + ClockCircleOutlined, + CodeOutlined, + InfoCircleFilled, + SendOutlined, + VideoCameraOutlined, +} from "@ant-design/icons"; +import CollaborativeEditor, { + CollaborativeEditorHandle, +} from "@/components/CollaborativeEditor/CollaborativeEditor"; +import { WebrtcProvider } from "y-webrtc"; +import { + ExecuteVisibleAndCustomTests, + ExecuteVisibleAndHiddenTestsAndSubmit, + ExecutionResults, + GetVisibleTests, + isTestResult, + SubmissionHiddenTestResultsAndStatus, + SubmissionResults, + Test, + TestData, + TestResult, +} from "@/app/services/execute"; +import { QuestionDetailFull } from "@/components/question/QuestionDetailFull/QuestionDetailFull"; +import VideoPanel from "@/components/VideoPanel/VideoPanel"; + +interface CollaborationProps {} + +export default function CollaborationPage(props: CollaborationProps) { + const router = useRouter(); + const providerRef = useRef(null); + const submissionProviderRef = useRef(null); + const executionProviderRef = useRef(null); + + const editorRef = useRef(null); + + const [isLoading, setIsLoading] = useState(false); + + // Code Editor States + const [historyDocRefId, setHistoryDocRefId] = useState( + undefined + ); + const [code, setCode] = useState(""); + const [questionTitle, setQuestionTitle] = useState( + undefined + ); + const [questionDocRefId, setQuestionDocRefId] = useState( + undefined + ); + const [complexity, setComplexity] = useState(undefined); + const [categories, setCategories] = useState([]); // Store the selected filter categories + const [description, setDescription] = useState(undefined); + const [selectedLanguage, setSelectedLanguage] = useState("python"); // State to hold the selected language item + + // Session states + const [collaborationId, setCollaborationId] = useState( + undefined + ); + const [currentUser, setCurrentUser] = useState(undefined); + const [matchedUser, setMatchedUser] = useState("Loading..."); + const [sessionDuration, setSessionDuration] = useState(0); // State for count-up timer (TODO: currently using localstorage to store time, change to db stored time in the future) + const stopwatchRef = useRef(null); + const [matchedTopics, setMatchedTopics] = useState( + undefined + ); + + useEffect(() => { + const storedTime = localStorage.getItem("session-duration"); + setSessionDuration(storedTime ? parseInt(storedTime) : 0); + }, []); + + // Chat states + const [messageToSend, setMessageToSend] = useState( + undefined + ); + + // Test case states + const [manualTestCase, setManualTestCase] = useState( + undefined + ); + const [visibleTestCases, setVisibleTestCases] = useState([]); + const [isLoadingTestCase, setIsLoadingTestCase] = useState(false); + const [isLoadingSubmission, setIsLoadingSubmission] = + useState(false); + const [ + submissionHiddenTestResultsAndStatus, + setSubmissionHiddenTestResultsAndStatus, + ] = useState(undefined); + + // End Button Modal state + const [isModalOpen, setIsModalOpen] = useState(false); + // Session End Modal State + const [isSessionEndModalOpen, setIsSessionEndModalOpen] = + useState(false); + const [countDown, setCountDown] = useState(5); + + // Stops the session duration stopwatch + const stopStopwatch = () => { + if (stopwatchRef.current) { + clearInterval(stopwatchRef.current); + } + }; + + // Starts the session duration stopwatch + const startStopwatch = () => { + if (stopwatchRef.current) { + clearInterval(stopwatchRef.current); + } + + stopwatchRef.current = setInterval(() => { + setSessionDuration((prevTime) => { + const newTime = prevTime + 1; + localStorage.setItem("session-duration", newTime.toString()); + return newTime; + }); + }, 1000); + }; + + // Convert seconds into time of format "hh:mm:ss" + const formatTime = (seconds: number) => { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = seconds % 60; + + return ( + (hours > 9 ? hours : "0" + hours) + + ":" + + (minutes > 9 ? minutes : "0" + minutes) + + ":" + + (secs > 9 ? secs : "0" + secs) + ); + }; + + // Message + const [messageApi, contextHolder] = message.useMessage(); + + const successMessage = (message: string) => { + messageApi.open({ + type: "success", + content: message, + }); + }; + + const infoMessage = (message: string) => { + messageApi.open({ + type: "info", + content: message, + }); + }; + + const errorMessage = (message: string) => { + messageApi.open({ + type: "error", + content: message, + }); + }; + + const sendSubmissionResultsToMatchedUser = (data: SubmissionResults) => { + if (!providerRef.current) { + throw new Error("Provider not initialized"); + } + providerRef.current.awareness.setLocalStateField("submissionResultsState", { + submissionResults: data, + id: Date.now(), + }); + }; + + const sendExecutingStateToMatchedUser = (executing: boolean) => { + if (!providerRef.current) { + throw new Error("Provider not initialized"); + } + providerRef.current.awareness.setLocalStateField("executingState", { + executing: executing, + id: Date.now(), + }); + }; + + const sendSubmittingStateToMatchedUser = (submitting: boolean) => { + if (!providerRef.current) { + throw new Error("Provider not initialized"); + } + providerRef.current.awareness.setLocalStateField("submittingState", { + submitting: submitting, + id: Date.now(), + }); + }; + + const sendExecutionResultsToMatchedUser = (data: ExecutionResults) => { + if (!providerRef.current) { + throw new Error("Provider not initialized"); + } + providerRef.current.awareness.setLocalStateField("executionResultsState", { + executionResults: data, + id: Date.now(), + }); + }; + + const updateSubmissionResults = (data: SubmissionResults) => { + const submissionHiddenTestResultsAndStatus: SubmissionHiddenTestResultsAndStatus = { + hiddenTestResults: data.hiddenTestResults, + status: data.status, + } + setSubmissionHiddenTestResultsAndStatus(submissionHiddenTestResultsAndStatus); + localStorage.setItem("submissionHiddenTestResultsAndStatus", JSON.stringify(submissionHiddenTestResultsAndStatus)); + setVisibleTestCases(data.visibleTestResults); + localStorage.setItem("visibleTestResults", JSON.stringify(data.visibleTestResults)); + }; + + const updateExecutionResults = (data: ExecutionResults) => { + setVisibleTestCases(data.visibleTestResults); + localStorage.setItem("visibleTestResults", JSON.stringify(data.visibleTestResults)); + }; + + const updateLangauge = (data: string) => { + setSelectedLanguage(data); + } + + const handleRunTestCases = async () => { + if (!questionDocRefId) { + throw new Error("Question ID not found"); + } + setIsLoadingTestCase(true); + sendExecutingStateToMatchedUser(true); + try { + const data = await ExecuteVisibleAndCustomTests(questionDocRefId, { + code: code, + language: selectedLanguage, + customTestCases: "", + }); + updateExecutionResults(data); + infoMessage("Test cases executed. Review the results below."); + sendExecutionResultsToMatchedUser(data); + } finally { + setIsLoadingTestCase(false); + sendExecutingStateToMatchedUser(false); + } + }; + + const handleSubmitCode = async () => { + if (!questionDocRefId) { + throw new Error("Question ID not found"); + } + setIsLoadingSubmission(true); + sendSubmittingStateToMatchedUser(true); + try { + const data = await ExecuteVisibleAndHiddenTestsAndSubmit(questionDocRefId, { + code: code, + language: selectedLanguage, + user: currentUser ?? "", + matchedUser: matchedUser ?? "", + matchedTopics: matchedTopics ?? [], + title: questionTitle ?? "", + questionDifficulty: complexity ?? "", + questionTopics: categories, + }); + updateExecutionResults({ + visibleTestResults: data.visibleTestResults, + customTestResults: [], + }); + updateSubmissionResults(data); + sendSubmissionResultsToMatchedUser(data); + successMessage("Code saved successfully!"); + } finally { + setIsLoadingSubmission(false); + sendSubmittingStateToMatchedUser(false); + } + }; + + const handleCodeChange = (code: string) => { + setCode(code); + }; + + // Fetch the question on initialisation + useEffect(() => { + if (!isLoading) { + setIsLoading(true); + } + + // Retrieve details from localstorage + const questionDocRefId: string = + localStorage.getItem("questionDocRefId") ?? ""; + const collabId: string = localStorage.getItem("collabId") ?? ""; + const matchedUser: string = localStorage.getItem("matchedUser") ?? ""; + const currentUser: string = localStorage.getItem("user") ?? ""; + const matchedTopics: string[] = + localStorage.getItem("matchedTopics")?.split(",") ?? []; + const submissionHiddenTestResultsAndStatus: SubmissionHiddenTestResultsAndStatus | undefined = + localStorage.getItem("submissionHiddenTestResultsAndStatus") + ? JSON.parse(localStorage.getItem("submissionHiddenTestResultsAndStatus") as string) + : undefined; + const visibleTestCases: Test[] = JSON.parse(localStorage.getItem("visibleTestResults") ?? "[]") ?? []; + + // Set states from localstorage + setCollaborationId(collabId); + setMatchedUser(matchedUser); + setCurrentUser(currentUser); + setMatchedTopics(matchedTopics); + setQuestionDocRefId(questionDocRefId); + setSubmissionHiddenTestResultsAndStatus(submissionHiddenTestResultsAndStatus); + setVisibleTestCases(visibleTestCases); + + GetSingleQuestion(questionDocRefId).then((data: Question) => { + setQuestionTitle(`${data.id}. ${data.title}`); + setComplexity(data.complexity); + setCategories(data.categories); + setDescription(data.description); + }); + + if (visibleTestCases.length == 0) { + GetVisibleTests(questionDocRefId).then((data: Test[]) => { + setVisibleTestCases(data); + }).catch((e) => { + errorMessage(e.message); + }); + } + + // Start stopwatch + startStopwatch(); + }, []); + + // useEffect for timer + useEffect(() => { + if (isSessionEndModalOpen && countDown > 0) { + const timer = setInterval(() => { + setCountDown((prevCountDown) => prevCountDown - 1); + }, 1000); + + return () => clearInterval(timer); // Clean up on component unmount or when countdown changes + } else if (countDown === 0) { + router.push("/matching"); // Redirect to matching page + } + }, [isSessionEndModalOpen, countDown]); + + // Tabs component items for visibleTestCases + var items: TabsProps["items"] = visibleTestCases.map((item, index) => { + return { + key: index.toString(), + label: ( + + Case {index + 1} + + ), + children: ( +
+ + {isTestResult(item) && ( +
+ + + {item.passed ? "Passed" : "Failed"} + +
+ Actual Output:{" "} + {item.actual} +
+ {item.error && ( + <> + Error: +
{item.error}
+ + )} +
+ )} +
+ ), + }; + }); + + // Handles the cleaning of localstorage variables, stopping the timer & signalling collab user on webrtc + // type: "initiator" | "peer" + const handleCloseCollaboration = (type: string) => { + // Stop stopwatch + stopStopwatch(); + if (editorRef.current && type === "initiator") { + editorRef.current.endSession(); // Call the method on the editor + } + + // Trigger modal open showing session end details + setIsSessionEndModalOpen(true); + + // Remove localstorage variables for collaboration + localStorage.removeItem("session-duration"); // TODO: Remove this after collaboration backend data stored + localStorage.removeItem("user"); + localStorage.removeItem("matchedUser"); + localStorage.removeItem("collabId"); + localStorage.removeItem("questionDocRefId"); + localStorage.removeItem("matchedTopics"); + localStorage.removeItem("submissionHiddenTestResultsAndStatus"); + localStorage.removeItem("visibleTestResults"); + localStorage.removeItem("editor-language"); // Remove editor language type when session closed + }; + + return ( + + {contextHolder} +
+ + +

+ The collaboration session has ended. You will be redirected in{" "} + {countDown} seconds +

+

+ Question:{" "} + {questionTitle} +

+

+ Difficulty:{" "} + + {complexity && + complexity.charAt(0).toUpperCase() + complexity.slice(1)} + +

+

+ Duration:{" "} + + {formatTime(sessionDuration)} + +

+

+ Matched User:{" "} + + {matchedUser} + +

+
+ + + + + + +
+
+
+ + Code +
+ {/* TODO: Link to execution service for code submission */} +
+
+ {isLoadingSubmission && } +
+ +
+
+ {collaborationId && currentUser && selectedLanguage && ( + + )} +
+ + + + + Session Status:{" "} + {submissionHiddenTestResultsAndStatus + ? submissionHiddenTestResultsAndStatus.status + : "Not Attempted"} + +
+ {submissionHiddenTestResultsAndStatus && ( + + Passed{" "} + { + submissionHiddenTestResultsAndStatus.hiddenTestResults + .passed + }{" "} + /{" "} + { + submissionHiddenTestResultsAndStatus.hiddenTestResults + .total + }{" "} + hidden test cases + + )} +
+
+
+ + + +
+
+
+ + Session Details +
+ handleCloseCollaboration("initiator")} + open={isModalOpen} + onCancel={() => setIsModalOpen(false)} + width={400} + > +

+ Are you sure you want to quit the existing collaboration + session? This will end the session for both users! +

+
+ +
+ +
+ Duration: + + {formatTime(sessionDuration)} + +
+
+ Matched User: + + {matchedUser} + +
+
+
+ +
+
+ + Video +
+ + {/*
+
+ Matched with {matchedUser} +
+
+
+
+ setMessageToSend(e.target.value)} + placeholder="Send Message Here" + rows={4} + /> +
*/} +
+
+ +
+
+ + ); +} diff --git a/apps/frontend/src/app/collaboration/[id]/styles.scss b/apps/frontend/src/app/collaboration/[id]/styles.scss new file mode 100644 index 0000000000..66999ff862 --- /dev/null +++ b/apps/frontend/src/app/collaboration/[id]/styles.scss @@ -0,0 +1,187 @@ +.collaboration-layout { + background: white; + display: flex; + flex-direction: column; + height: 100vh; +} + +.collaboration-content { + background: white; + flex: 1; + overflow-y: auto; +} + +.first-col, +.second-col, +.third-col { + height: 90vh; +} + +.collab-row { + padding: 0rem 1rem; +} + +.code-row { + height: 100%; + padding: 1rem 0.25rem 0.25rem; +} + +.session-row { + height: 18%; + padding: 1rem 0.25rem 0.25rem; +} + +.chat-row { + height: 82%; + padding: 0.25rem; +} + +.test-top-container, +.code-top-container, +.session-top-container { + display: flex; + justify-content: space-between; +} + +.code-container { + border: 2px solid #463f3a; + border-radius: 10px; + width: 100%; + padding: 1rem; +} + +// .code-second-container { +// display: flex; +// margin: 4px 0px; +// } + +.code-language { + margin: auto 8px auto 0px; +} + +.language-select { + width: 120px; +} + +.session-container { + border: 2px solid #463f3a; + border-radius: 10px; + width: 100%; + padding: 1rem; +} + +.chat-container { + border: 2px solid #463f3a; + border-radius: 10px; + width: 100%; + padding: 1rem; +} + +.chat-message-box { + margin-top: 1rem; + height: 365px; + border: 1px solid #d9d9d9; + border-radius: 6px; + overflow-y: scroll; +} + +.chat-header-message { + font-size: 14px; + color: #c6c6c6; + margin: 3px auto; + text-align: center; +} + +.chat-typing-box { + margin-top: 1rem; +} + +.question-title, +.test-title, +.code-title, +.session-title, +.chat-title { + font-size: 16px; + font-weight: bold; +} + +.question-difficulty, +.question-topic { + margin: 4px 0px; +} + +.session-duration, +.session-matched-user-label { + margin: 4px 0px; +} + +.session-duration-timer { + font-weight: normal; + margin-left: 8px; +} + +.session-matched-user-name { + color: #e0afa0; + margin-left: 8px; +} + +.topic-label, +.session-duration, +.session-matched-user-label { + font-size: 14px; + font-weight: bold; +} + +.title-icons { + margin-right: 4px; +} + +.code-submit-button, +.session-end-button, +.test-case-button { + width: fit-content; +} + +.modal-description { + margin-bottom: 2rem; +} + +.session-modal-question, +.session-modal-difficulty, +.session-modal-duration, +.session-modal-matched-user { + font-weight: bold; +} + +.session-modal-time, +.session-modal-title { + font-weight: normal; +} +.session-modal-matched-user-name { + color: #e0afa0; +} + +.info-modal-icon { + color: red; +} + +.test-result-container { + margin-top: 10px; +} + +.hidden-test-icon { + margin-right: 5px; +} + +.error-message { + overflow-x: auto; + overflow-y: auto; // Allows vertical scroll if content exceeds max-height + white-space: nowrap; + max-width: 100%; + padding: 4px; + min-height: 50px; // Adjust height as needed + max-height: 150px; // Adjust max height for scrollable area + border: 1px solid #ddd; // Optional: add a border for visibility + border-radius: 4px; // Optional: round the corners slightly + background-color: #f9f9f9; // Optional: light background color +} diff --git a/apps/frontend/src/app/history/page.tsx b/apps/frontend/src/app/history/page.tsx new file mode 100644 index 0000000000..e881b3118b --- /dev/null +++ b/apps/frontend/src/app/history/page.tsx @@ -0,0 +1,191 @@ +"use client"; +import Header from "@/components/Header/header"; +import { Layout, message, PaginationProps, Row, Table, Tag } from "antd"; +import { Content } from "antd/es/layout/layout"; +import { HistoryOutlined } from "@ant-design/icons"; +import "./styles.scss"; +import { useEffect, useState } from "react"; +import React from "react"; +import { useRouter } from "next/navigation"; +import { GetUserHistories, History } from "@/app/services/history"; +import { ValidateUser, VerifyTokenResponseType } from "@/app/services/user"; + +interface TablePagination { + totalCount: number; + totalPages: number; + currentPage: number; + limit: number; +} + +export default function QuestionPage() { + // Message States + const [messageApi, contextHolder] = message.useMessage(); + + const error = (message: string) => { + messageApi.open({ + type: "error", + content: message, + }); + }; + + const router = useRouter(); + + const [username, setUsername] = useState(undefined); + const [userQuestionHistories, setUserQuestionHistories] = + useState(); + const [isHistoryLoading, setIsHistoryLoading] = useState(true); + const [paginationParams, setPaginationParams] = useState({ + totalCount: 0, + totalPages: 0, + currentPage: 1, + limit: 10, + }); + + // Handler for change in page jumper + const onPageJump: PaginationProps["onChange"] = (pageNumber) => { + setPaginationParams((prev) => { + loadQuestionHistories(pageNumber, prev.limit); + return { ...paginationParams, currentPage: pageNumber }; + }); + }; + + // Handler for show size change for pagination + const onShowSizeChange: PaginationProps["onShowSizeChange"] = ( + current, + pageSize + ) => { + setPaginationParams((prev) => { + loadQuestionHistories(current, pageSize); + return { ...paginationParams, currentPage: current, limit: pageSize }; + }); + }; + + async function loadQuestionHistories(currentPage: number, limit: number) { + if (username === undefined) return; + setIsHistoryLoading(true); + GetUserHistories(username, currentPage, limit) + .then((data: any) => { + setUserQuestionHistories(data.histories); + setPaginationParams({ + ...paginationParams, + totalCount: data.totalCount, + totalPages: data.totalPages, + currentPage: data.currentPage, + limit: data.limit, + }); + }) + .finally(() => { + setIsHistoryLoading(false); + }); + } + + useEffect(() => { + loadQuestionHistories(paginationParams.currentPage, paginationParams.limit); + }, [username]); + + useEffect(() => { + ValidateUser().then((data: VerifyTokenResponseType) => { + setUsername(data.data.username); + }); + }, []); + + const columns = [ + { + title: "Question Title", + dataIndex: "title", + key: "title", + }, + { + title: "Categories", + dataIndex: "questionTopics", + key: "questionTopics", + render: (categories: string[]) => + categories.map((category) => {category}), + }, + { + title: "Difficulty", + dataIndex: "questionDifficulty", + key: "questionDifficulty", + render: (difficulty: string) => { + let color = ""; + if (difficulty === "easy") { + color = "#2DB55D"; + } else if (difficulty === "medium") { + color = "orange"; + } else if (difficulty === "hard") { + color = "red"; + } + return ( +
+ {difficulty.charAt(0).toUpperCase() + difficulty.slice(1)} +
+ ); + }, + }, + { + title: "Submitted at", + dataIndex: "createdAt", + key: "createdAt", + render: (date: string) => { + return new Date(date).toLocaleString(); + }, + }, + { + title: "Language", + dataIndex: "language", + key: "language", + }, + { + title: "Matched with", + dataIndex: "matchedUser", + key: "matchedUser", + }, + ]; + + const handleRowClick = (h: History) => { + // Link to page + // questionId is just read as "history", as only the doc ref id is involved in requests + // If the question database is reloaded, then the questionDocRefId may not be correct + router.push( + `/question/history?data=${h.questionDocRefId}&history=${h.historyDocRefId}` + ); + }; + + return ( +
+ {contextHolder} + +
+ +
+
+
Submission History
+
+
+ { + return { + onClick: () => handleRowClick(record), + style: { cursor: "pointer" }, + }; + }} + loading={isHistoryLoading} + pagination={{ + current: paginationParams.currentPage, + total: paginationParams.totalCount, + pageSize: paginationParams.limit, + onChange: onPageJump, + showSizeChanger: true, + onShowSizeChange: onShowSizeChange, + }} + /> + + + + + + ); +} diff --git a/apps/frontend/src/app/history/styles.scss b/apps/frontend/src/app/history/styles.scss new file mode 100644 index 0000000000..3d435ad375 --- /dev/null +++ b/apps/frontend/src/app/history/styles.scss @@ -0,0 +1,64 @@ +.layout { + background: white; +} + +.content { + background: white; +} + +.content-card { + margin: 4rem 8rem; + padding: 2rem 4rem; + background-color: white; + min-height: 100vh; + border-radius: 30px; + box-shadow: 0px 10px 60px rgba(226, 236, 249, 0.5); +} + +.content-row-1 { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.content-title { + // font-family: "Poppins"; + // font-style: normal; + font-weight: 600; + font-size: 22px; + line-height: 33px; + letter-spacing: -0.01em; + color: #000000; +} + +.content-filter { + margin: 1.5rem 0; +} + +.edit-button { + margin-right: 0.5rem; +} + +.filter-button { + margin-left: 8px; + box-shadow: 0 2px 0 rgba(0, 0, 0, 0.02) !important; +} + +.clear-button { + width: 100%; +} + +.categories-multi-select, +.difficulty-select, +.order-select { + width: 100%; +} + +.create-title, +.new-problem-categories-multi-select, +.new-problem-difficulty-select, +.create-description, +.create-problem-id { + width: 100%; + margin: 5px; +} diff --git a/apps/frontend/src/app/layout.tsx b/apps/frontend/src/app/layout.tsx index 61070cf842..e68cebe340 100644 --- a/apps/frontend/src/app/layout.tsx +++ b/apps/frontend/src/app/layout.tsx @@ -25,9 +25,7 @@ const RootLayout = ({ }} > - - {children} - + {children} diff --git a/apps/frontend/src/app/matching/MatchingModal.tsx b/apps/frontend/src/app/matching/MatchingModal.tsx index 1a243e58cc..cf8db123e7 100644 --- a/apps/frontend/src/app/matching/MatchingModal.tsx +++ b/apps/frontend/src/app/matching/MatchingModal.tsx @@ -1,5 +1,9 @@ +"use client" + import React, { useState, useEffect } from 'react'; import { + Form, + Button, Modal, } from 'antd'; import 'typeface-montserrat'; @@ -11,17 +15,43 @@ import JoinedMatchContent from './modalContent/JoinedMatchContent'; import MatchNotFoundContent from './modalContent/MatchNotFoundContent'; import MatchCancelledContent from './modalContent/MatchCancelledContent'; import useMatching from '../services/use-matching'; +import { ValidateUser } from '../services/user'; +import { useTimer } from 'react-timer-hook'; +import { useRouter } from 'next/navigation'; interface MatchingModalProps { - isOpen: boolean; - close: () => void; + isOpen: boolean; + close: () => void; +} + +export interface MatchParams { + topics: string[], + difficulties: string[], } +const MATCH_TIMEOUT = 30; +const JOIN_TIMEOUT = 5; const MatchingModal: React.FC = ({ isOpen, close: _close }) => { const matchingState = useMatching(); const [closedType, setClosedType] = useState<"finding" | "cancelled" | "joined">("finding"); - const [timeoutAfter, setTimeoutAfter] = useState(9999); - const isClosable = ["timeout", "closed"].includes(matchingState.state); + const isClosable = ["timeout", "closed"].includes(matchingState.state) && closedType != "joined"; + const router = useRouter(); + const { totalSeconds, pause: pauseTimer, restart: restartTimer } = useTimer({ + expiryTimestamp: new Date(Date.now() + MATCH_TIMEOUT * 1000), + autoStart: false, + onExpire() { + if (matchingState.state === "matching") { + matchingState.timeout(); + return; + } + if (matchingState.state === "found") { + join(); + return; + } + console.warn(`matching is in ${matchingState.state}`) + }, + }); + const passed = MATCH_TIMEOUT - totalSeconds; function close() { // clean up matching and closedType State @@ -32,64 +62,101 @@ const MatchingModal: React.FC = ({ isOpen, close: _close }) _close(); } + const startMatch = matchingState.state == "closed" || matchingState.state == "timeout" ? async (params: MatchParams): Promise => { + const user = await ValidateUser(); + + restartTimer( + new Date(Date.now() + MATCH_TIMEOUT * 1000), + ); + + matchingState.start({ + email: user.data.email, + username: user.data.username, + type: "match_request", + ...params + }); + } : undefined; + + const join = matchingState.state == "found" ? (() => { + matchingState.ok(); + setClosedType("joined"); + localStorage.setItem("user", matchingState.info.user); + localStorage.setItem( + "matchedUser", + matchingState.info.matchedUser + ); + localStorage.setItem("collabId", matchingState.info.matchId); + localStorage.setItem("questionDocRefId", matchingState.info.questionDocRefId); + localStorage.setItem("matchedTopics", matchingState.info.matchedTopics.join(",")); + + // Redirect to collaboration page + router.push(`/collaboration/${matchingState.info.matchId}`); + }) : () => { throw new Error("join called when not found"); } + + useEffect(() => { + if (matchingState.state === "cancelling" || matchingState.state === "timeout") { + pauseTimer(); + return; + } + if (matchingState.state === "found") { + restartTimer( + new Date(Date.now() + JOIN_TIMEOUT * 1000), + ) + } + }, [matchingState]) + const renderModalContent = () => { switch (matchingState.state) { case 'closed': switch (closedType) { case "finding": - return ; + return {}}/>; case "cancelled": return { setClosedType("finding"); }} - retry={() => {}} - canceledIn={timeoutAfter} + canceledIn={passed} />; case "joined": return { - setClosedType("cancelled"); - }} - name1={matchingState.info?.myName || ""} - name2={matchingState.info?.partnerName || ""} + cancel={() => { + setClosedType("cancelled"); + }} + name1={matchingState.info?.user ?? ""} + name2={matchingState.info?.matchedUser ?? ""} />; } case 'matching': return { + cancelMatch={() => { setClosedType("cancelled"); - setTimeoutAfter(timeoutAfter); matchingState.cancel(); + pauseTimer(); }} - timeout={(timeoutAfter: number) => { - matchingState.timeout() - setTimeoutAfter(timeoutAfter); - }} + timePassed={passed} />; case 'cancelling': - return {}} timeout={() => {}}/>; + return {}} timePassed={passed}/>; case 'starting': return {}}/> case 'found': - return { matchingState.ok(); setClosedType("cancelled"); }} - join={() => { - matchingState.ok(); - setClosedType("joined"); - }} - name1={matchingState.info.myName} - name2={matchingState.info.partnerName} - /> + join={join} + name1={matchingState.info.user} + name2={matchingState.info.matchedUser} + joiningIn={totalSeconds} + /> case 'timeout': - return {}} timedOutIn={10}/>; + return ; default: throw new Error('Invalid matching state.'); } - }; + } return ( = ({ isOpen, close: _close }) maskClosable={false} className="modal" > - {renderModalContent()} + + name="match" + onFinish={startMatch} + initialValues={{ + topics: [], + difficulties: [], + }}> + {renderModalContent()} + {isClosable && ( )} diff --git a/apps/frontend/src/app/matching/modalContent/FindMatchContent.tsx b/apps/frontend/src/app/matching/modalContent/FindMatchContent.tsx index ac65cbe033..393500569f 100644 --- a/apps/frontend/src/app/matching/modalContent/FindMatchContent.tsx +++ b/apps/frontend/src/app/matching/modalContent/FindMatchContent.tsx @@ -3,6 +3,8 @@ import { Tag, Select, Space, + Form, + Button, } from 'antd'; import { DifficultyOption, @@ -13,119 +15,60 @@ import 'typeface-montserrat'; import './styles.scss'; import { ValidateUser } from "@/app/services/user" import { type MatchRequestParams } from '@/app/services/use-matching'; - -interface DifficultySelectorProps { - className?: string; - selectedDifficulties: string[]; - onChange: (difficulties: string[]) => void; -} - -interface TopicSelectorProps { - className?: string; - selectedTopics: string[]; - onChange: (topics: string[]) => void; -} +import { MatchParams } from '../MatchingModal'; interface Props { beginMatch(request: MatchRequestParams): void } const FindMatchContent: React.FC = ({ beginMatch }) => { - const [selectedDifficulties, setSelectedDifficulties] = useState([]); - const [selectedTopics, setSelectedTopics] = useState([]); - const [isLoading, setIsLoading] = useState(false); - - const handleDifficultyChange = (difficulties: string[]) => { - setSelectedDifficulties(difficulties); - }; - - const handleTopicChange = (topics: string[]) => { - setSelectedTopics(topics); - }; - return (
Find Match
Difficulty
- + name="difficulties"> + {/* @ts-ignore Not required to pass value and onChange as since this is handled by Form.Item wrapper*/} + +
Topic
- + + name="topics" + > + - -
- ) +const TagInput: React.FC<{ + value: string[], + onChange(value: string[]): void, +}> = ({ value, onChange }) => { + return <> + {DifficultyOption.map(difficultyOption => ( + { + onChange( + enabled + ? [...value, difficultyOption.value] + : value.filter(diff => diff !== difficultyOption.value) + ) + }} + > + {difficultyOption.label} + + ))} + } export default FindMatchContent; diff --git a/apps/frontend/src/app/matching/modalContent/JoinedMatchContent.tsx b/apps/frontend/src/app/matching/modalContent/JoinedMatchContent.tsx index 55e40888f1..c8498bca75 100644 --- a/apps/frontend/src/app/matching/modalContent/JoinedMatchContent.tsx +++ b/apps/frontend/src/app/matching/modalContent/JoinedMatchContent.tsx @@ -9,18 +9,22 @@ import 'typeface-montserrat'; import './styles.scss'; import { handleCancelMatch } from '../handlers'; import { formatTime } from '@/utils/DateTime'; +import { useStopwatch } from 'react-timer-hook'; interface Props { - cancel(): void - name1: string, // user's username - name2: string, // matched user's username + cancel(): void; + name1: string; // user's username + name2: string; // matched user's username } const JoinedMatchContent: React.FC = ({cancel, name1: me, name2: you}) => { const matchAlreadyJoined = () => { throw new Error('Match already joined.'); } + const { totalSeconds } = useStopwatch({ + autoStart: true + }) return (
@@ -45,20 +49,15 @@ const JoinedMatchContent: React.FC = ({cancel, name1: me, name2: you}) =>
Match Found!
- Waiting for others... {formatTime(83)} + Waiting for others... {formatTime(totalSeconds)}
-
- ) -} + ); +}; export default JoinedMatchContent; diff --git a/apps/frontend/src/app/matching/modalContent/MatchCancelledContent.tsx b/apps/frontend/src/app/matching/modalContent/MatchCancelledContent.tsx index ca7cff5273..12c750acf1 100644 --- a/apps/frontend/src/app/matching/modalContent/MatchCancelledContent.tsx +++ b/apps/frontend/src/app/matching/modalContent/MatchCancelledContent.tsx @@ -3,14 +3,14 @@ import 'typeface-montserrat'; import './styles.scss'; import { handleReselectMatchOptions, handleRetryMatch } from '../handlers'; import { formatTime } from '@/utils/DateTime'; +import { Button, Form } from 'antd'; interface Props { - retry(): void, reselect(): void, canceledIn: number, } -const MatchCancelledContent: React.FC = ({retry, reselect, canceledIn}) => { +const MatchCancelledContent: React.FC = ({reselect, canceledIn}) => { return (
@@ -28,11 +28,11 @@ const MatchCancelledContent: React.FC = ({retry, reselect, canceledIn}) =
Your match request has been cancelled after waiting {formatTime(canceledIn)}
- {/* */} + + + - + */}
- ) -} + ); +}; export default MatchFoundContent; diff --git a/apps/frontend/src/app/matching/modalContent/MatchNotFoundContent.tsx b/apps/frontend/src/app/matching/modalContent/MatchNotFoundContent.tsx index e0a817dae5..75891971c3 100644 --- a/apps/frontend/src/app/matching/modalContent/MatchNotFoundContent.tsx +++ b/apps/frontend/src/app/matching/modalContent/MatchNotFoundContent.tsx @@ -3,13 +3,14 @@ import 'typeface-montserrat'; import './styles.scss'; import { handleReselectMatchOptions, handleRetryMatch } from '../handlers'; import { formatTime } from '@/utils/DateTime'; +import { Button, Form } from 'antd'; +import { MatchParams } from '../MatchingModal'; const MatchNotFoundContent: React.FC<{ - retry(): void, reselect(): void, timedOutIn: number, }> = ({ - retry, reselect, timedOutIn + reselect, timedOutIn }) => { return (
@@ -28,11 +29,11 @@ const MatchNotFoundContent: React.FC<{
Sorry, we could not find a match after {formatTime(timedOutIn)}
- {/* */} + noStyle> + +
@@ -169,7 +144,11 @@ const ProfilePage = (props: ProfilePageProps): JSX.Element => { - + (true); // Store the states related to table's loading // Message States const [messageApi, contextHolder] = message.useMessage(); - const error = (message: string) => { + const errorMessage = (message: string) => { messageApi.open({ type: "error", content: message, }); }; - // Retrieve the docRefId from query params during page navigation - const searchParams = useSearchParams(); - const docRefId: string = searchParams?.get("data") ?? ""; + const editorRef = useRef(null); + const languageConf = new Compartment(); + // Retrieve the questionDocRefId and historyDocRefId from query params during page navigation + const searchParams = useSearchParams(); + const questionDocRefId: string = searchParams?.get("data") ?? ""; + const historyDocRefId: string = searchParams?.get("history") ?? ""; // Code Editor States const [questionTitle, setQuestionTitle] = useState( undefined @@ -56,33 +60,61 @@ export default function Home() { const [complexity, setComplexity] = useState(undefined); const [categories, setCategories] = useState([]); // Store the selected filter categories const [description, setDescription] = useState(undefined); - const [selectedItem, setSelectedItem] = useState("python"); // State to hold the selected language item - - // used to check if user JWT is verified - - const [userId, setUserId] = useState(undefined); - const [email, setEmail] = useState(undefined); const [username, setUsername] = useState(undefined); - const [isAdmin, setIsAdmin] = useState(undefined); + const [userQuestionHistories, setUserQuestionHistories] = + useState(); + const [submission, setSubmission] = useState(); + const [isHistoryLoading, setIsHistoryLoading] = useState(true); + const [isSubmissionLoading, setIsSubmissionLoading] = useState(true); + const [currentSubmissionId, setCurrentSubmissionId] = useState< + string | undefined + >(historyDocRefId == "" ? undefined : historyDocRefId); + const [paginationParams, setPaginationParams] = useState({ + totalCount: 0, + totalPages: 0, + currentPage: 1, + limit: 3, + }); + const [visibleTestCases, setVisibleTestCases] = useState([]); - const router = useRouter(); - - useLayoutEffect(() => { - var isAuth = false; + const state = EditorState.create({ + doc: "", + extensions: [ + basicSetup, + languageConf.of(javascript()), + EditorView.theme({ + "&": { height: "100%", overflow: "hidden" }, // Enable Scroll + }), + EditorView.editable.of(false), // Disable editing + ], + }); - ValidateUser().then((data: VerifyTokenResponseType) => { - setUserId(data.data.id); - setEmail(data.data.email); - setUsername(data.data.username); - setIsAdmin(data.data.isAdmin); - isAuth = true; - }).finally(() => { - if(!isAuth){ - // cannot verify - router.push('/login'); // Client-side redirect using router.push - } + // Handler for change in page jumper + const onPageJump: PaginationProps["onChange"] = (pageNumber) => { + setPaginationParams((prev) => { + loadQuestionHistories(pageNumber, prev.limit); + return { ...paginationParams, currentPage: pageNumber }; }); - }, [router]) + }; + + async function loadQuestionHistories(currentPage: number, limit: number) { + if (username === undefined) return; + setIsHistoryLoading(true); + GetUserQuestionHistories(username, questionDocRefId, currentPage, limit) + .then((data: any) => { + setUserQuestionHistories(data.histories); + setPaginationParams({ + ...paginationParams, + totalCount: data.totalCount, + totalPages: data.totalPages, + currentPage: data.currentPage, + limit: data.limit, + }); + }) + .finally(() => { + setIsHistoryLoading(false); + }); + } // When code editor page is initialised, fetch the particular question, and display in code editor useEffect(() => { @@ -90,156 +122,242 @@ export default function Home() { setIsLoading(true); } - GetSingleQuestion(docRefId).then((data: any) => { - setQuestionTitle(data.title); - setComplexity(data.complexity); - setCategories(data.categories); - setDescription(data.description); + GetSingleQuestion(questionDocRefId) + .then((data: any) => { + setQuestionTitle(data.title); + setComplexity(data.complexity); + setCategories(data.categories); + setDescription(data.description); + }) + .catch((e) => { + errorMessage(e.message); + }) + .finally(() => { + setIsLoading(false); + }); + + GetVisibleTests(questionDocRefId) + .then((data: Test[]) => { + setVisibleTestCases(data); + }) + .catch((e) => { + errorMessage(e.message); + }); + }, [questionDocRefId]); + + useEffect(() => { + loadQuestionHistories(paginationParams.currentPage, paginationParams.limit); + }, [username]); + + useEffect(() => { + ValidateUser().then((data: VerifyTokenResponseType) => { + setUsername(data.data.username); }); - }, [docRefId]); + }, []); + + useEffect(() => { + // Only show history if a history is selected + if (currentSubmissionId === undefined) return; + + const view = new EditorView({ + state, + parent: editorRef.current || undefined, + }); + + setIsSubmissionLoading(true); + GetHistory(currentSubmissionId) + .then((data: any) => { + const submittedAt = new Date(data.createdAt); + setSubmission({ + submittedAt: submittedAt.toLocaleString("en-US"), + language: data.language, + matchedUser: + username == data.matchedUser ? data.user : data.matchedUser, + otherUser: data.user, + historyDocRefId: data.historyDocRefId, + code: data.code, + }); + setIsSubmissionLoading(false); + + view.dispatch( + state.update({ + changes: { from: 0, to: state.doc.length, insert: data.code }, + }) + ); + }) + .finally(() => {}); + + return () => { + // Cleanup on component unmount + view.destroy(); + }; + }, [currentSubmissionId]); + + const columns = [ + { + title: "Submitted at", + dataIndex: "createdAt", + key: "createdAt", + render: (date: string) => { + return new Date(date).toLocaleString(); + }, + }, + { + title: "Language", + dataIndex: "language", + key: "language", + }, + { + title: "Matched with", + dataIndex: "matchedUser", + key: "matchedUser", + }, + ]; + + const handleRowClick = (s: Submission) => { + setCurrentSubmissionId(s.historyDocRefId); + }; return (
{contextHolder} - +
- - -
- -
-
-

- {questionTitle} -

- - Solved  - - -
-
- - {complexity && - complexity.charAt(0).toUpperCase() + - complexity.slice(1)} - -
-
- Topics: - {categories.map((category) => ( - {category} - ))} -
-
- {description} -
-
-
- -
-
-

Testcases

- -
-
- - - -
-
- -
- - - -
-
-

- -  Session Details -

- -
-
-
- Start Time: - 01:23:45 -
- - Session Duration:{" "} - - 01:23:45 -
- Matched with: - John Doe + {currentSubmissionId && ( + +
+ {isSubmissionLoading && ( +
+ +
+ )} +
+
+
+ + Submitted Code +
+
+ + {/* Details of submission */} +
+
+
+ Submitted at: +
+
+ {submission?.submittedAt || "-"} +
+
+
+
Language:
+
+ {submission?.language || "-"} +
+
+
+
+ Matched with: +
+
+ {submission?.matchedUser + ? // Check to ensure that matched user is correct, otherwise swap with otherUser + username == submission.matchedUser + ? submission.otherUser + : submission.matchedUser + : "-"} +
+
+
+ + {/* Code Editor */} +
+
+
-
- - -
-
-

- -  Chat -

-
-
-
+ + )} diff --git a/apps/frontend/src/app/question/[id]/styles.scss b/apps/frontend/src/app/question/[id]/styles.scss index 764ed1f0c3..343f2bd3e9 100644 --- a/apps/frontend/src/app/question/[id]/styles.scss +++ b/apps/frontend/src/app/question/[id]/styles.scss @@ -1,247 +1,84 @@ -// start of code editor classes -.code-editor-layout { - background: white !important; - height: 97vh; - overflow: hidden; -} - -.code-editor-content { +.question-layout { + background: white; display: flex; flex-direction: column; + height: 100vh; } -.entire-page { +.question-content { + background: white; flex: 1; - padding: 1rem; -} - -.problem-description { - height: 70%; -} - -.code-editor { - height: 100.5% !important; -} - -.test-cases { - height: 30%; + // overflow-y: auto; } -.session-details { - height: 20%; +.first-col, +.second-col { + height: 90vh; } -.chat-box { - height: 80%; +.question-row { + padding: 0rem 1rem; } -.boxes { - border-radius: 10px; - border: solid; - margin: 4px; - padding: 5px; +.history-row { + padding: 0.5rem 0.25rem 0.25rem; + height: 40%; } -.problem-description-info { - width: 100%; - height: 100%; - overflow: auto; - margin-left: 5px; - padding: 8px; -} - -.problem-description-top { - display: inline-flex; - flex-wrap: nowrap; - width: 100%; -} - -.problem-description-title { - padding: 0%; - margin: 0%; -} - -.problem-solve-status { - margin-left: auto; +.code-row { + flex: 1; + height: 60%; + padding: 0.5rem 0.25rem 0.25rem; } -.test-cases-div { - height: 100%; - width: 100%; +.test-top-container { display: flex; - flex-direction: column; - padding: 8px; + justify-content: space-between; + height: 50%; } -.test-cases-top { - display: inline-flex; - flex-wrap: nowrap; - margin-bottom: 5px; -} - -.testcase-title { - padding: 0%; - padding-left: 1%; - margin: 0%; -} - -.runtestcases-button { - margin-left: auto; -} - -.testcase-buttons { +.history-container, +.code-container { + border: 2px solid #463f3a; + border-radius: 10px; width: 100%; - padding-left: 1%; - display: inline-flex; - gap: 5px; -} - -.testcase-code-div { - flex: 1 1 auto; - display: flex; - flex-direction: column; - justify-content: flex-end; - margin-top: 10px; -} - -.testcase-code { - // max-height: 100%; - // height: 100%; - // padding-top: 4px; - // padding-left: 1%; - // flex:1; - height: 100%; - flex: 1 1 auto; -} - -// .code-editor-box { -// height: 100% !important; -// flex-direction: column; -// display: flex; -// margin-bottom: 4px !important; -// } - -.code-editor-div { height: 100%; - width: 100%; - display: flex; - flex-direction: column; - padding: 8px; -} - -.code-editor-title { - padding: 0%; - padding-left: 1%; - margin: 0%; -} - -.code-editor-code-div { - flex: 1 1 auto; - display: flex; - flex-direction: column; - justify-content: flex-end; - margin-top: 10px; -} - -.code-editor-code { - height: 100%; - flex: 1 1 auto; + padding: 1rem; } -.code-editor-top { - display: inline-flex; - flex-wrap: nowrap; +.history-language { + margin: auto 8px auto 0px; } .language-select { - display: inline-flex; - flex-wrap: nowrap; - margin-top: 4px; -} - -.submit-solution-button { - margin-left: auto; -} - -.select-language-button { - width: max-content; -} - -.session-details-title { - padding: 0%; - padding-left: 1%; - margin: 0%; -} - -.session-details-top { - display: inline-flex; - flex-wrap: nowrap; -} - -.session-details-div { - height: 100%; - width: 100%; - display: flex; - flex-direction: column; - padding: 8px; + width: 120px; } -.end-session-button { - margin-left: auto; -} - -.session-details-text-div { - flex: 1 1 auto; - display: flex; - flex-direction: column; - justify-content: flex-end; - margin-top: 10px; -} - -.session-details-text { - height: 100%; - flex: 1 1 auto; - margin-left: 5px; -} - -.chat-box-div { - height: 100%; - width: 100%; - display: flex; - flex-direction: column; - padding: 8px; -} - -.chat-box-top { - display: inline-flex; - flex-wrap: nowrap; -} - -.chat-box-title { - padding: 0%; - padding-left: 1%; - margin: 0%; +.question-title, +.test-title, +.code-title, +.history-title { + font-size: 16px; + font-weight: bold; } -.complexity-div { - margin: 4px 0; +.question-difficulty, +.question-topic { + margin: 4px 0px; } -.topic-label, -.language-text { +.topic-label { + font-size: 14px; font-weight: bold; } -.tag-container { - margin: 4px 0; -} - -.description-text { - text-align: justify; - text-justify: inter-word; - line-height: 1.3; +.title-icons { + margin-right: 4px; } -.session-headers { - font-weight: bold; +.submission-header-detail { + font-weight: normal; + display: flex; + flex-direction: row; + padding: 0px 10px 0px 0px; } diff --git a/apps/frontend/src/app/question/page.tsx b/apps/frontend/src/app/question/page.tsx index 8fbb9502ea..3e1e1048f0 100644 --- a/apps/frontend/src/app/question/page.tsx +++ b/apps/frontend/src/app/question/page.tsx @@ -14,12 +14,18 @@ import { Tag, Modal, Form, + Tabs, + Checkbox, + Tooltip, + Card, + Spin, } from "antd"; import { Content } from "antd/es/layout/layout"; import { DeleteOutlined, EditOutlined, PlusCircleOutlined, + PlusOutlined, SearchOutlined, } from "@ant-design/icons"; import "./styles.scss"; @@ -40,7 +46,15 @@ import { import Link from "next/link"; import TextArea from "antd/es/input/TextArea"; import { ValidateUser, VerifyTokenResponseType } from "../services/user"; -import { useRouter } from "next/navigation"; +import TabPane from "antd/es/tabs/TabPane"; +import { + CreateTestcases, + DeleteTestcases, + ReadAllTestcases, + TestData, + UpdateTestcases, +} from "../services/execute"; +import { v4 as uuidv4 } from "uuid"; /** * defines the State of the page whe a user is deleing an object. Has 3 general states: @@ -50,6 +64,10 @@ import { useRouter } from "next/navigation"; */ type DeletionStage = {} | { index: Question; deleteConfirmed: boolean }; +type Test = TestData & { + key?: string; +}; + function DeleteModal({ isDeleting, questionTitle, @@ -79,7 +97,7 @@ function DeleteModal({ ); } -export default function Home() { +export default function QuestionListPage() { // State of Deletion const [deletionStage, setDeletionStage] = useState({}); @@ -118,33 +136,17 @@ export default function Home() { // used to check if user JWT is verified - const [userId, setUserId] = useState(undefined); - const [email, setEmail] = useState(undefined); - const [username, setUsername] = useState(undefined); const [isAdmin, setIsAdmin] = useState(undefined); - const router = useRouter(); - useLayoutEffect(() => { - var isAuth = false; - - ValidateUser() - .then((data: VerifyTokenResponseType) => { - setUserId(data.data.id); - setEmail(data.data.email); - setUsername(data.data.username); - setIsAdmin(data.data.isAdmin); - isAuth = true; - }) - .finally(() => { - if (!isAuth) { - // cannot verify - router.push("/login"); // Client-side redirect using router.push - } - }); - }, [router]); + ValidateUser().then((data: VerifyTokenResponseType) => { + setIsAdmin(data.data.isAdmin); + }); + }, []); const handleEditClick = (index: number, question: Question) => { + fetchTestsForQuestion(question.docRefId); + // Open the modal for the specific question const updatedModals = isEditModalOpen && isEditModalOpen.map((_, idx) => idx === index); @@ -174,7 +176,18 @@ export default function Home() { docRefId: string ) => { try { - const editedQuestion = await EditQuestion(values, docRefId); + const editedQuestion = await EditQuestion( + { + title: values.title, + description: values.description, + categories: values.categories, + complexity: values.complexity, + }, + docRefId + ); + + await UpdateTestcases(docRefId, visibleTests, hiddenTests); + // Reset form or update UI as needed handleModalClose(index); editForm.resetFields(); @@ -187,7 +200,20 @@ export default function Home() { const handleCreateQuestion = async (values: NewQuestion) => { try { - const createdQuestion = await CreateQuestion(values); + const createdQuestion = await CreateQuestion({ + title: values.title, + description: values.description, + categories: values.categories, + complexity: values.complexity, + }); + + await CreateTestcases( + createdQuestion.docRefId, + createdQuestion.title, + visibleTests, + hiddenTests + ); + // Reset form or update UI as needed setIsNewProblemModelOpen(false); form.resetFields(); @@ -199,6 +225,8 @@ export default function Home() { }; const showNewProblemModal = () => { + setVisibleTests([{ key: uuidv4(), input: "", expected: "" }]); + setHiddenTests([]); setIsNewProblemModelOpen(true); }; @@ -266,6 +294,100 @@ export default function Home() { return () => clearTimeout(timeout); }, [search]); + // Testcases + + const [visibleTests, setVisibleTests] = useState([]); + const [hiddenTests, setHiddenTests] = useState([]); + const [isTestsLoading, setIsTestsLoading] = useState(true); + const [activeKey, setActiveKey] = useState(undefined); + const [testActiveKey, setTestActiveKey] = useState("1"); + + const handleAddVisibleTest = () => { + const newKey = uuidv4(); + setVisibleTests([ + ...visibleTests, + { key: newKey, input: "", expected: "" }, + ]); + setActiveKey(newKey); + }; + + const handleAddHiddenTest = () => { + const newKey = uuidv4(); + setHiddenTests([...hiddenTests, { key: newKey, input: "", expected: "" }]); + setActiveKey(newKey); + }; + + const handleRemoveVisibleTest = (targetKey: string) => { + if (visibleTests.length > 1) { + setVisibleTests( + visibleTests.filter((test: Test) => test.key !== targetKey) + ); + } + }; + + const handleRemoveHiddenTest = (targetKey: string) => { + setHiddenTests(hiddenTests.filter((test: Test) => test.key !== targetKey)); + }; + + const handleTestChange = ( + type: string, + index: number, + input?: string, + expected?: string + ) => { + // Determine which array to update based on the type (visible or hidden) + if (type === "visible") { + const updatedTests = [...visibleTests]; + updatedTests[index].input = input ?? updatedTests[index].input; + updatedTests[index].expected = expected ?? updatedTests[index].expected; + setVisibleTests(updatedTests); + } else if (type === "hidden") { + const updatedTests = [...hiddenTests]; + updatedTests[index].input = input ?? updatedTests[index].input; + updatedTests[index].expected = expected ?? updatedTests[index].expected; + setHiddenTests(updatedTests); + } + }; + + async function fetchTestsForQuestion(questionId: string) { + setIsTestsLoading(true); + ReadAllTestcases(questionId) + .then((response) => { + const { visibleTests, hiddenTests } = response; + + // Add a unique key to each test + if (visibleTests) { + const keyedVisibleTests = visibleTests.map((test, index) => ({ + ...test, + key: uuidv4(), + })); + setVisibleTests(keyedVisibleTests); + setActiveKey(keyedVisibleTests[0].key); + setTestActiveKey("1"); + } + if (hiddenTests) { + setHiddenTests( + hiddenTests.map((test) => ({ + ...test, + key: uuidv4(), + })) + ); + } + }) + .catch((err) => { + error("Error fetching tests for question."); + }) + .finally(() => { + setIsTestsLoading(false); + }); + } + + async function deleteTestsByDocRefId(docRefId: string) { + await DeleteTestcases(docRefId).catch((e) => { + error("Error deleting tests associated with the question."); + }); + } + // Table column specification var columns: TableProps["columns"]; if (isAdmin) { @@ -287,7 +409,7 @@ export default function Home() { query: { data: question.docRefId }, // the data }} > - + {text} ), }, @@ -361,7 +483,7 @@ export default function Home() { }, ]} > -