diff --git a/.dockerignore b/.dockerignore index 6b8710a7..3376adaf 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,43 @@ -.git +# Ignore VSCode4Teaching unnecesary directories +**/v4t-course/ + +# Ignore Maven build directories +**/target/ +**/logs/ +**/tmp/ + +# Ignore Node/Angular build directories and dependencies +**/node_modules/ +**/dist/ +**/.angular/ +**/.cache/ + +# Ignore IDE specific directories and files +**/.vscode/ +**/.idea/ +**/.project +**/.classpath + +# Ignore documentation files +**/*.md + +# Ignore MacOS specific files +**/.DS_Store +**/.AppleDouble +**/.LSOverride + +# Ignore other unnecesary system files +**/Thumbs.db +**/ehthumbs.db +**/Desktop.ini +**/$RECYCLE.BIN/ + +# Ignore git files +**/.git/ +**/.gitignore + +# Ignore Dockerfile and Dockerignore themselves +**/docker-compose.yml +**/compose.yml +**/Dockerfile +**/.dockerignore diff --git a/vscode4teaching-server/docker/.env b/.env similarity index 93% rename from vscode4teaching-server/docker/.env rename to .env index f66c826e..5df27853 100644 --- a/vscode4teaching-server/docker/.env +++ b/.env @@ -1,16 +1,22 @@ SERVER_PORT=8080 + MYSQL_ROOT_PASSWORD=T4cwGK3q5NdR3vMz MYSQL_DATABASE=vscode4teach + SPRING_DATASOURCE_USERNAME=vscode4teach SPRING_DATASOURCE_PASSWORD=AvScnGQp5e4GHnd SPRING_DATASOURCE_URL=jdbc:mysql://db/vscode4teach?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC + JWT_SECRET=vscode4teaching + V4T_FILEDIRECTORY=/app/v4t_courses + FILE_INITIALIZATION=false DATA_INITIALIZATION=false SPRING_JPA_HIBERNATE_DDL_AUTO=create-drop + SUPERUSER_USERNAME=admin -SUPERUSER_PASSWORD=admin +SUPERUSER_PASSWORD=adminpassword SUPERUSER_EMAIL=admin@admin.com SUPERUSER_NAME=Admin SUPERUSER_LASTNAME=Admin diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 00000000..7fdd394d --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,26 @@ +# VSCode4Teaching +**Developer documentation on the Continuous Integration, Delivery and Deployment (*CI/CD*) system of this project** + +This project is configured with a continuous integration, deployment and delivery system through [GitHub Actions](https://github.com/features/actions). This system comprises two *workflows*: one for the extension (file [`extension.ci.yml`](extension.ci.yml)) and one for the server and web application (file [`server.ci.yml`](server.ci.yml)). + +### Detailed description of declared jobs +The aforementioned files define the jobs that will be executed sequentially based on the type of changes registered in the repository. These jobs are: +- `test`. This job runs when changes are pushed to the `master` (or `main`) and `develop` branches or when a *pull request* is opened targeting any of these branches. After setting up the execution environment on the pipeline runner, it executes the complete test suite implemented in the component on which the pipeline is launched, returning success if the tests pass. +- `publish`. This job runs when a change is made to the `master` branch, meaning a new version of VSCode4Teaching is being released. This step requires the deployed version to be correctly configured in each component's manifest, which are POM (`pom.xml`) for the server, and `package.json` for both the extension and web application. + - For the extension, this job will compile and package the extension source code into VSIX format and deploy it to the [Visual Studio Code Marketplace](https://marketplace.visualstudio.com/VSCode) in its new version. This job will fail if a version with the same identifier has already been deployed previously or if the authentication token has expired or is invalid for publishing. + - For the server, it will execute a Docker image build process defined through the `Dockerfile` file, which is *multistage*: it will build the web application in a first Node container to produce the static files (HTML, CSS, JS) needed to run the application without Node, compile the server source code along with the frontend static resources in a second Maven container with JDK 11, and use a third container to define the final image on JDK 11, setting the *entrypoint* command to run the JAR obtained in the previous stage. The resulting image is tagged with the version declared in the POM and also as `latest`. Both images are pushed to the specified [Docker Hub](https://hub.docker.com) repository. +- `deploy`. Executed only under the same conditions as `publish`, and declared just in the server workflow, this job allows deploying the built Docker image to the defined production server. It connects via SSH to the remote computer, copies the `docker-compose.yml` file, pulls the new image generated in the previous job and restarts the Docker Compose instance running the configured production server. + +### Parameterized definition of variables and secrets +To run the previously explained jobs, certain variables and secrets must be properly defined in the repository: +- Publishing the server image to Docker Hub. These variables are used during the server (embedding the built web application) image publishing to Docker Hub and subsequent production deployment. + - `DOCKER_HUB_USERNAME` (variable). The username used to create the Docker image for the VSCode4Teaching server (and web application as static frontend resource). It will be the first part of the image name (preceding the `/`) and must match the name specified in the `docker-compose.yml` file to ensure successful production deployment. + - `DOCKER_HUB_IMAGE_NAME` (variable). Properly, the name of the image (following the `/` that separates it from the username). It must match the name used in the Docker Compose definition to complete the deployment job correctly. + - `DOCKER_HUB_PAT` (secret). The personal access token required to publish the built image to Docker Hub. If it is incorrect or invalid, the pipeline execution will fail. +- Deploying on the production server. These variables are used to access the remote computer that hosts the production server. + - `EDUKAFORA_HOST` (variable). The address to which the pipeline executor will connect via SSH during deployment through the default port (22). + - `EDUKAFORA_USER` (variable). The user hosting the VSCode4Teaching project on the production machine. + - `EDUKAFORA_SSH_KEY` (secret). The private key used to authenticate the SSH connection. + - `EDUKAFORA_PATH` (variable). The path, preferably absolute, on the production environment where the existing deployment is located and will be replaced. +- Publishing the extension to the Visual Studio Code Marketplace. This variable is used in the extension workflow for publishing new versions. + - `VSCODE_MARKETPLACE_PAT` (secret). The personal access token required to authenticate the publication to the Visual Studio Code Marketplace. It has a scheduled expiration, so it may need to be replaced or renewed if an error occurs during the pipeline execution. The definition of the extension name, publisher, and other deployment and publication details are contained in its `package.json`, and the configured token must be able to authenticate and published according to the parameters set in the manifest file. diff --git a/.github/workflows/extension.ci.yml b/.github/workflows/extension.ci.yml new file mode 100644 index 00000000..55d91b30 --- /dev/null +++ b/.github/workflows/extension.ci.yml @@ -0,0 +1,61 @@ +name: "Extension pipeline" + +on: + push: + branches: + - master + - main + - develop + pull_request: + branches: + - master + - main + - develop + +jobs: + test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./vscode4teaching-extension + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + - name: Set up Node version + uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + - name: Build + run: npm run build + - name: Test + run: npm run test + publish: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./vscode4teaching-extension + if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main') }} + needs: test + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + - name: Set up Node version + uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Install dependencies and VSCode extension manager (vsce) + run: npm ci && npm install -g @vscode/vsce + - name: Check VSCode Marketplace token validity + run: npx vsce verify-pat + env: + VSCE_PAT: ${{ secrets.VSCODE_MARKETPLACE_PAT }} + - name: Publish to VSCode Marketplace + run: npx vsce publish --allow-star-activation + env: + VSCE_PAT: ${{ secrets.VSCODE_MARKETPLACE_PAT }} diff --git a/.github/workflows/server.ci.yml b/.github/workflows/server.ci.yml new file mode 100644 index 00000000..d7604ce6 --- /dev/null +++ b/.github/workflows/server.ci.yml @@ -0,0 +1,108 @@ +name: "Server pipeline" + +on: + push: + branches: + - master + - main + - develop + pull_request: + branches: + - master + - main + - develop + +jobs: + test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./vscode4teaching-server + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up Java version + uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: temurin + - name: Test + run: ./mvnw clean dependency:resolve test + publish: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main') }} + needs: test + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + - name: Get deployed version from POM + run: | + VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout) + echo VERSION=$VERSION >> $GITHUB_ENV + working-directory: ./vscode4teaching-server + - name: Build Docker image + run: | + env | grep DOCKER + DOCKER_IMAGE="$DOCKER_HUB_USERNAME/$DOCKER_HUB_IMAGE_NAME" + echo DOCKER_IMAGE=$DOCKER_IMAGE >> $GITHUB_ENV + docker build -t $DOCKER_IMAGE:$VERSION -t $DOCKER_IMAGE:latest . + env: + DOCKER_HUB_USERNAME: ${{ vars.DOCKER_HUB_USERNAME }} + DOCKER_HUB_IMAGE_NAME: ${{ vars.DOCKER_HUB_IMAGE_NAME }} + VERSION: ${{ env.VERSION }} + - name: Push to Docker Hub + run: | + echo $DOCKER_HUB_PAT | docker login -u $DOCKER_HUB_USERNAME --password-stdin + docker push $DOCKER_IMAGE:$VERSION + docker push $DOCKER_IMAGE:latest + env: + DOCKER_HUB_USERNAME: ${{ vars.DOCKER_HUB_USERNAME }} + DOCKER_IMAGE: ${{ env.DOCKER_IMAGE }} + VERSION: ${{ env.VERSION }} + DOCKER_HUB_PAT: ${{ secrets.DOCKER_HUB_PAT }} + + deploy: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main') }} + needs: publish + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + - name: Setup SSH agent to connect to deploy server + run: | + mkdir -p ~/.ssh + echo "${EDUKAFORA_SSH_KEY}" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan -t ed25519 ${EDUKAFORA_HOST} >> ~/.ssh/known_hosts + env: + EDUKAFORA_SSH_KEY: ${{ secrets.EDUKAFORA_SSH_KEY }} + EDUKAFORA_HOST: ${{ vars.EDUKAFORA_HOST }} + - name: Prepare new VSCode4Teaching version + run: | + scp docker-compose.yml ${EDUKAFORA_USER}@${EDUKAFORA_HOST}:${EDUKAFORA_PATH}/compose-new.yml + ssh ${EDUKAFORA_USER}@${EDUKAFORA_HOST} "cd ${EDUKAFORA_PATH} && docker compose -f compose-new.yml pull --policy missing" + env: + EDUKAFORA_HOST: ${{ vars.EDUKAFORA_HOST }} + EDUKAFORA_USER: ${{ vars.EDUKAFORA_USER }} + EDUKAFORA_PATH: ${{ vars.EDUKAFORA_PATH }} + - name: Stop VSCode4Teaching and change by new version + run: | + ssh ${EDUKAFORA_USER}@${EDUKAFORA_HOST} "cd ${EDUKAFORA_PATH} && docker compose down" + ssh ${EDUKAFORA_USER}@${EDUKAFORA_HOST} "cd ${EDUKAFORA_PATH} && mv compose.yml compose-old.yml && mv compose-new.yml compose.yml" + ssh ${EDUKAFORA_USER}@${EDUKAFORA_HOST} "cd ${EDUKAFORA_PATH} && docker compose up -d" + env: + EDUKAFORA_USER: ${{ vars.EDUKAFORA_USER }} + EDUKAFORA_HOST: ${{ vars.EDUKAFORA_HOST }} + EDUKAFORA_PATH: ${{ vars.EDUKAFORA_PATH }} + - name: Clean up old version and SSH keys + run: | + ssh ${EDUKAFORA_USER}@${EDUKAFORA_HOST} "cd ${EDUKAFORA_PATH} && rm compose-old.yml" + rm -r ~/.ssh + env: + EDUKAFORA_USER: ${{ vars.EDUKAFORA_USER }} + EDUKAFORA_HOST: ${{ vars.EDUKAFORA_HOST }} + EDUKAFORA_PATH: ${{ vars.EDUKAFORA_PATH }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ae59d0cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +**/.DS_Store +**/*.env* \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4413260f..00000000 --- a/.travis.yml +++ /dev/null @@ -1,47 +0,0 @@ -dist: trusty -branches: - only: - - master -jobs: - include: - - name: V4T Server (Spring Boot) - language: java - jdk: oraclejdk11 - services: - - docker - before_script: - - cd ./vscode4teaching-server/ - - chmod +x mvnw - script: - - "./mvnw clean package -B -q" - after_script: - - cd .. - - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin - - docker build -t vscode4teaching/vscode4teaching:2.2.1 . - - docker build -t vscode4teaching/vscode4teaching:latest . - - docker push vscode4teaching/vscode4teaching:2.2.1 - - docker push vscode4teaching/vscode4teaching:latest - - name: V4T Extension (Node.js) - language: node_js - os: - - linux - - osx - node_js: 16.16.0 - install: - - | - if [ $TRAVIS_OS_NAME == "linux" ]; then - export DISPLAY=':99.0' - /usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & - fi - before_script: - - cd ./vscode4teaching-extension - - npm install --save-dev - script: - - npm test - cache: - npm: false - -env: - global: - - secure: Xubi//N9TBKtampk7kO3V0rvuhbCIzVJ7ad2oJIoZGVUyCgmt9KdK46jfG2eSUYuvOMKg1xrXia0R0YC+VveoJPiRgCxqNIfqhb605XaY0SLRXAh2cThABs6q3IwFzoFVIG/8/xhZpqSngbystQxm8EflGhYdZYukijqtEEx78VrtuWaPRBcxGBFt8GaaLyzWvy8eY6Fzw4KZntZPHaalLt0t5k+Qt+Dt2AJuUb3YV1IaWyMQIeF0dKM2XLMpUISeRBVzPrqSmRxpbMyR+8iktPY2KYJFmeH1MD8H1imG/OVkHxSRiXsPXkZ8ueqXSzZ1R9cNIR6ZLKRIyo2sMvxRz5Kzr2fhcEtxCtyRW0sXFV4EG7lsUewn9E9MJ7e5OtnwZOKkcIvStydrwd3t9TH+pUdptgVGA+EdL/T4mcIjPkzYWPoUoLn1MYR3YYUezvqsJXrgl4HqQJSSLgaCEso/q/s5+IH+1y4xFYpLNo05+OhMWz0vNCBbPT02VC+6YTQwHAOcGNjOHDwgkwkg1KC46ZMkbdfm4bhNL1oWfAPynuHjNAfinzxc0kE/FataPXVEt7XX+dA3YdQL+KffqKdRLQqDgsQ1ZNvE+oZwJT1NMfIhlsreboBl1C9diQrxfLwaHBZjUXuNpbI6FK8W8iyQvBNfPWvcdKdMmrNdQV7268= - - secure: nWKjhXkCD7qAjXAO//DYZXAcC5dDVtX+EgxQOAyCoyTfw0hdtxIHbn2iylpPlkd9iLObEkK1R30TWXsteZMHyBHJEgJM+YueuVqTUXKgS+EK6F0PebvgjyhZzfIZrTWOmhieHC/cXmDLeJbVkO+9++5KYGj0nYxkURqVXrguYlU7g+InbE8/raYjWsmpUVjhaEd245fyT2jmwSiaxiu8tWa9KuawsQeS/CVSNYAZaXfpLUk2ZOqOOxtR1TWfAv+4C84i6u+KhUS9FI6Li5MBMIZkU+npyx9gWw2EzmQf2ciqPYIGyRUe50tx5HLufZZ31RYae0jowuy7UpOQckNOG+hUxdzMCGX1pwh2mgKEKFL1XC8AsFo+rIt9eJhRaJN+hAQ8VtAhc8VzrdQhSn1CPgrT3ePO3wjyjo49MZjOrWd9q6r1DSXYQcOURFa2iUp+v5z4RFbAAyW/eAaEXlUr62ACQrQqZ5gGqchRYm2W2a5T+gua7ToyXO4yNzCt27hUv6c4kgtS7ATm1PKvD0/oDA6k6Roix4BJER38ONdp6sj8fmuEUfvTnS5QmVh8Zg4rf4htBGPriE/UFF2SSvj4kYj5ZKVFYeP4wGMhiGjADSp81LVdrSnGe7P7HoYEX3CGnukabV9+3b3eOXr+iy4e0UxZLsTp8Gs8T85Qqc6Kpc0= diff --git a/Dockerfile b/Dockerfile index 175a4831..80a0ef49 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,20 @@ # Step 1: Compilation of Angular frontend # It will be embedded as a static resource into Spring Boot backend -FROM node:16.13.2 AS angular +FROM node:18 AS angular COPY vscode4teaching-webapp /usr/src/app WORKDIR /usr/src/app RUN ["npm", "install"] RUN ["npm", "run", "build"] # Step 2: Compilation of Maven project (generation of JAR) -FROM maven:3.8.4-jdk-11 AS builder +FROM maven:3.9.7-eclipse-temurin-11 AS builder COPY vscode4teaching-server /data -COPY --from=angular /usr/src/app/dist /data/src/main/resources/static +COPY --from=angular /usr/src/app/dist/vscode4teaching /data/src/main/resources/static/ WORKDIR /data RUN ["mvn", "clean", "package"] # Step 3: Generation of Docker image using the JAR previously built -FROM adoptopenjdk/openjdk11:latest -RUN apt-get update && apt-get install -y netcat && rm -rf /var/lib/apt/lists/* -COPY --from=builder /data/target/vscode4teaching-server-*.jar ./app/vscode4teaching-server-*.jar -COPY vscode4teaching-server/docker/waitDB.sh ./app/waitDB.sh +FROM eclipse-temurin:11 +COPY --from=builder /data/target/vscode4teaching-server-*.jar ./app/vscode4teaching-server.jar EXPOSE 8080 -RUN ["chmod", "+x", "./app/waitDB.sh"] -CMD ["./app/waitDB.sh"] +ENTRYPOINT [ "java", "-jar", "./app/vscode4teaching-server.jar" ] \ No newline at end of file diff --git a/README.md b/README.md index bcb69977..52c17e72 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ # VSCode4Teaching -[![Travis CI build status](https://img.shields.io/travis/com/codeurjc-students/2019-VSCode4Teaching?label=Travis%20CI&style=flat-square)](https://app.travis-ci.com/github/codeurjc-students/2019-VSCode4Teaching) +[![VSCode server CI/CD status at master branch](https://img.shields.io/github/actions/workflow/status/codeurjc-students/2019-VSCode4Teaching/server.ci.yml?branch=master&style=flat-square&label=Server%20CI%2FCD%20(master))](https://github.com/codeurjc-students/2019-VSCode4Teaching/actions/workflows/server.ci.yml?query=branch%3Amaster) +[![VSCode server CI status at develop branch](https://img.shields.io/github/actions/workflow/status/codeurjc-students/2019-VSCode4Teaching/server.ci.yml?branch=develop&style=flat-square&label=Server%20CI%20(develop))](https://github.com/codeurjc-students/2019-VSCode4Teaching/actions/workflows/server.ci.yml?query=branch%3Adevelop) [![Docker Hub Repository](https://img.shields.io/docker/v/vscode4teaching/vscode4teaching?color=0db7ed&label=Docker%20Hub&sort=date&style=flat-square)](https://hub.docker.com/r/vscode4teaching/vscode4teaching) [![Docker Hub pulls](https://img.shields.io/docker/pulls/vscode4teaching/vscode4teaching?color=0db7ed&label=Docker%20Hub%20pulls&style=flat-square)](https://hub.docker.com/r/vscode4teaching/vscode4teaching) + +[![VSCode extension CI/CD status at master branch](https://img.shields.io/github/actions/workflow/status/codeurjc-students/2019-VSCode4Teaching/extension.ci.yml?branch=master&style=flat-square&label=Extension%20CI%2FCD%20(master))](https://github.com/codeurjc-students/2019-VSCode4Teaching/actions/workflows/extension.ci.yml?query=branch%3Amaster) +[![VSCode extension CI status at develop branch](https://img.shields.io/github/actions/workflow/status/codeurjc-students/2019-VSCode4Teaching/extension.ci.yml?branch=develop&style=flat-square&label=Extension%20CI%20(develop))](https://github.com/codeurjc-students/2019-VSCode4Teaching/actions/workflows/extension.ci.yml?query=branch%3Adevelop) [![VS Marketplace extension's version](https://img.shields.io/visual-studio-marketplace/v/vscode4teaching.vscode4teaching?color=0078d7&label=VS%20Marketplace&style=flat-square)](https://marketplace.visualstudio.com/items?itemName=VSCode4Teaching.vscode4teaching) [![VS Marketplace extension's installs](https://img.shields.io/visual-studio-marketplace/i/vscode4teaching.vscode4teaching?color=0078d7&label=VS%20Marketplace%20installs&style=flat-square)](https://marketplace.visualstudio.com/items?itemName=VSCode4Teaching.vscode4teaching) @@ -46,7 +50,7 @@ VSCode4Teaching is composed of three components that work cooperatively with eac ### How to quickly start up a server To set up a VSCode4Teaching server, the fastest method is to use **Docker**, which is a lightweight container-based technology to speed up application deployment. For this purpose, some relevant files are inserted into the repository: - A [``Dockerfile``](Dockerfile) file containing the necessary coding to compile the webapp and insert it as a server view, which is compiled and launched in a Java container. The image resulting from this compilation is published in [*Docker Hub*](https://hub.docker.com/r/vscode4teaching/vscode4teaching) each time a new version of the application is released. -- A file [``vscode4teaching-server/docker/docker-compose.yml``](vscode4teaching-server/docker/docker-compose.yml) that allows using Docker Compose to quickly run two containers: one for the MySQL database used (``db``), for the image compiled from the ``Dockerfile`` above (``app``) and for the execution of a graphical database manager (``adminer``), which is optional and can be removed without affecting the operation of the server. +- A file [``vscode4teaching-server/docker/docker-compose.yml``](docker-compose.yml) that allows using Docker Compose to quickly run two containers: one for the MySQL database used (``db``) and another for the image compiled from the ``Dockerfile`` above (``app``), which is optional and can be removed without affecting the operation of the server. - A file [``vscode4teaching-server/docker/.env``](vscode4teaching-server/docker/.env) with user-customizable environment variables for the execution of the ``docker-compose.yml`` above. Therefore, it is possible to run a VSCode4Teaching server directly using the ``docker-compose.yml`` file, by pointing a terminal to the directory containing it and running the command ``docker compose up -d`` (or ``docker-compose up -d`` in earlier versions of Docker). diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..f5a14f63 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +name: vscode4teaching + +services: + app: + image: vscode4teaching/vscode4teaching:2.2.1 + depends_on: + - db + env_file: + - path: .env + required: true + ports: + - ${SERVER_PORT}:${SERVER_PORT} + volumes: + - ./volume-v4t:${V4T_FILEDIRECTORY} + restart: on-failure:6 + db: + image: mysql:8.4.0 + restart: on-failure:3 + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${SPRING_DATASOURCE_USERNAME} + MYSQL_PASSWORD: ${SPRING_DATASOURCE_PASSWORD} + volumes: + - ./volume-mysql:/var/lib/mysql diff --git a/vscode4teaching-extension/.dockerignore b/vscode4teaching-extension/.dockerignore deleted file mode 100644 index 5deac037..00000000 --- a/vscode4teaching-extension/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -out -node_modules -.vscode-test/ -*.vsix -.scannerwork/ -.nyc_output/ -coverage/ -tslint_report.txt \ No newline at end of file diff --git a/vscode4teaching-extension/src/extension.ts b/vscode4teaching-extension/src/extension.ts index 1a878602..446fe054 100644 --- a/vscode4teaching-extension/src/extension.ts +++ b/vscode4teaching-extension/src/extension.ts @@ -352,7 +352,7 @@ export function activate(context: vscode.ExtensionContext) { return; } } - vscode.window.showErrorMessage("Not performable action. Please try downloading exercise and accessing Dashboard."); + vscode.window.showErrorMessage("Not performabble action. Please try downloading exercise and accessing Dashboard."); }); const showCurrentExerciseDashboard = vscode.commands.registerCommand("vscode4teaching.showcurrentexercisedashboard", () => { diff --git a/vscode4teaching-server/.dockerignore b/vscode4teaching-server/.dockerignore deleted file mode 100644 index d4dfde66..00000000 --- a/vscode4teaching-server/.dockerignore +++ /dev/null @@ -1,31 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/** -!**/src/test/** - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ - -### VS Code ### -.vscode/ \ No newline at end of file diff --git a/vscode4teaching-server/.gitignore b/vscode4teaching-server/.gitignore index d4dfde66..54aac479 100644 --- a/vscode4teaching-server/.gitignore +++ b/vscode4teaching-server/.gitignore @@ -3,6 +3,7 @@ target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/** !**/src/test/** +**/src/main/resources/static ### STS ### .apt_generated @@ -28,4 +29,4 @@ target/ build/ ### VS Code ### -.vscode/ \ No newline at end of file +.vscode/ diff --git a/vscode4teaching-server/README.md b/vscode4teaching-server/README.md index d1e8ab7f..43f3a66f 100644 --- a/vscode4teaching-server/README.md +++ b/vscode4teaching-server/README.md @@ -35,7 +35,7 @@ The VSCode4Teaching server can be built in two ways: as a conventional Java appl ### Common Java application As the Spring Boot application makes use of Maven, it is possible to build the Java application in the following way: -- Build the web application and insert the result as a static resource of the *backend* (in the ``src/main/resources/static`` directory). If this process is not performed, the application will be built successfully but executing the web application's dependent features (such as the custom help page or the teacher registration by invitation) will not be possible. +- Build the web application and insert the result as a static resource of the *backend* (in the ``src/main/resources/static`` directory). If this process is not performed, the application will be built successfully but web application will not be available as frontend of this VSCode4Teaching server instance. - Generate a JAR executable using the following command: ``` mvn package @@ -43,9 +43,9 @@ As the Spring Boot application makes use of Maven, it is possible to build the J When the execution is finished, a JAR file can be found in the ``target`` directory. ### Docker -On the other hand, a [``Dockerfile``](Dockerfile) file is provided. It contains the necessary coding to build the webapp and insert it as a view in the server, which is built and launched in a Java container. +On the other hand, a [``Dockerfile``](Dockerfile) file is provided at the root of the project. It defines the necessary steps to build the webapp and copy it as the server frontend, which is built and then launched in a Java container. -To build it, it is necessary to execute the following command: +To build it, it is necessary to execute the following command (at the root of the project): ``` docker build . ``` @@ -54,7 +54,7 @@ This image resulting from this building process is also published in [*Docker Hu Note: the application is accessible through port 8080 by default. To modify this behavior, it is possible to enter an argument when executing the program (read more information in section [Arguments and environment variables](#arguments-and-environment-variables)). -On the other hand, in addition, a file [``docker-compose.yml``](vscode4teaching-server/docker/docker-compose.yml) is introduced. It allows to use Docker Compose to run two containers: one for the MySQL database used (``db``) and another for the image built from the ``Dockerfile`` file above (``app``). In addition, a third container is introduced for running a graphical database manager (``adminer``), which is optional and can be removed without affecting the operation of the server. +In addition, a file [``docker-compose.yml``](../docker-compose.yml) is introduced. It allows to use Docker Compose (version 2.0 or higher) to run two containers: one for the MySQL database used (aliased ``db``) and another for the image built from the ``Dockerfile`` file above (alias ``app``). ## Execution @@ -68,12 +68,12 @@ Depending on the building mode of the application, it is possible to execute the ``` mvn spring-boot:run ``` -- On the other hand, in case of using Docker, it is advisable to use Docker Compose to launch both the database and the application in containers. To do this, go to the directory containing the ``docker-compose.yml`` file and run the following command: +- On the other hand, in case of using Docker, it is advisable to use Docker Compose to launch both the database and the application in containers. To do this, go to the root path of the project, which contains the ``docker-compose.yml`` file, and run the following command: ``` docker-compose up -d ``` -On the other hand, to run the tests implemented in this component, it is possible to execute the following command: +On the other hand, to only run the tests implemented in this component, it is possible to execute the following command: ``` mvn test ``` diff --git a/vscode4teaching-server/docker/docker-compose.yml b/vscode4teaching-server/docker/docker-compose.yml deleted file mode 100644 index 20fc2a4c..00000000 --- a/vscode4teaching-server/docker/docker-compose.yml +++ /dev/null @@ -1,35 +0,0 @@ -version: "3.9" - -services: - db: - image: mysql - command: --default-authentication-plugin=mysql_native_password - volumes: - - db_data:/var/lib/mysql - restart: always - environment: - MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} - MYSQL_DATABASE: ${MYSQL_DATABASE} - MYSQL_USER: ${SPRING_DATASOURCE_USERNAME} - MYSQL_PASSWORD: ${SPRING_DATASOURCE_PASSWORD} - app: - image: vscode4teaching/vscode4teaching:latest - links: - - db - depends_on: - - db - env_file: - - .env - ports: - - "${SERVER_PORT}:${SERVER_PORT}" - volumes: - - v4t_courses:${V4T_FILEDIRECTORY} - restart: always - adminer: - image: adminer - restart: always - ports: - - "9000:8080" -volumes: - db_data: {} - v4t_courses: {} diff --git a/vscode4teaching-server/docker/waitDB.sh b/vscode4teaching-server/docker/waitDB.sh deleted file mode 100644 index fe71a24f..00000000 --- a/vscode4teaching-server/docker/waitDB.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -until nc -z -v -w30 db 3306 -do - echo "Waiting for database connection..." - sleep 5 -done -java -jar /app/vscode4teaching-server-*.jar \ No newline at end of file diff --git a/vscode4teaching-server/mvnw b/vscode4teaching-server/mvnw old mode 100644 new mode 100755 diff --git a/vscode4teaching-server/mvnw.cmd b/vscode4teaching-server/mvnw.cmd old mode 100644 new mode 100755 diff --git a/vscode4teaching-server/src/main/java/com/vscode4teaching/vscode4teachingserver/RequestInterceptor.java b/vscode4teaching-server/src/main/java/com/vscode4teaching/vscode4teachingserver/RequestInterceptor.java new file mode 100644 index 00000000..39f94246 --- /dev/null +++ b/vscode4teaching-server/src/main/java/com/vscode4teaching/vscode4teachingserver/RequestInterceptor.java @@ -0,0 +1,69 @@ +package com.vscode4teaching.vscode4teachingserver; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +@Configuration +public class RequestInterceptor implements WebMvcConfigurer { + + private final ApplicationContext applicationContext; + + public RequestInterceptor(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + private static final Set pathsHandledBySpring = new HashSet<>() {{ + // VSCode4Teaching app defined resources (all both API and WS endpoints) + add(Pattern.compile("/api/.*")); + add(Pattern.compile("/dashboard-refresh.*")); + add(Pattern.compile("/liveshare.*")); + + // Spring Boot documentation (OpenAPI auto-generated doc and Swagger UI) + add(Pattern.compile("/swagger-.*")); + add(Pattern.compile("/v3/api-docs.*")); + }}; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new HandlerInterceptor() { + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String requestURI = request.getRequestURI(); + + // If incoming request maps to any path handled by Spring or to a static resource, let framework handle it + if (pathsHandledBySpring.stream().anyMatch(pattern -> pattern.matcher(requestURI).matches()) + || isStaticResource(requestURI) + ) { + return true; + } + // Otherwise, request should be handled by Angular frontend + else { + request.getRequestDispatcher("/index.html").forward(request, response); + return false; + } + } + + private boolean isStaticResource(String requestURI) { + String staticResourcePath = "classpath:/static" + requestURI; + Resource resource = applicationContext.getResource(staticResourcePath); + try { + return resource.exists() && resource.isReadable(); + } catch (Exception e) { + return false; + } + } + }); + } + +} diff --git a/vscode4teaching-server/src/main/java/com/vscode4teaching/vscode4teachingserver/controllers/ViewController.java b/vscode4teaching-server/src/main/java/com/vscode4teaching/vscode4teachingserver/controllers/ViewController.java deleted file mode 100644 index 28a7268f..00000000 --- a/vscode4teaching-server/src/main/java/com/vscode4teaching/vscode4teachingserver/controllers/ViewController.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.vscode4teaching.vscode4teachingserver.controllers; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -@CrossOrigin -public class ViewController { - @GetMapping({"", "/"}) - public String redirect() { - return "forward:/app"; - } - - @GetMapping({"/app/**/{path:[^.]*}", "/{path:app[^.]*}"}) - public String serveAngularWebapp() { - return "forward:/index.html"; - } -} diff --git a/vscode4teaching-server/src/main/java/com/vscode4teaching/vscode4teachingserver/services/websockets/SocketHandler.java b/vscode4teaching-server/src/main/java/com/vscode4teaching/vscode4teachingserver/services/websockets/SocketHandler.java index bd5b2717..4cd9a1de 100644 --- a/vscode4teaching-server/src/main/java/com/vscode4teaching/vscode4teachingserver/services/websockets/SocketHandler.java +++ b/vscode4teaching-server/src/main/java/com/vscode4teaching/vscode4teachingserver/services/websockets/SocketHandler.java @@ -15,7 +15,10 @@ import java.io.IOException; import java.net.URI; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; @Component @@ -81,7 +84,7 @@ public void afterConnectionClosed(WebSocketSession session, CloseStatus status) } public void refreshExerciseDashboards(Set teachers) { - logger.info("SocketHandler: changes happened in a course or exercise, notifying teachers " + teachers.toString() + "..."); + logger.info("Exercise user info updated, sending updates to teachers " + teachers.toString() + "..."); for (User teacher : teachers) { sessions.stream() .filter(t -> t.isOpen() && Objects.requireNonNull(t.getPrincipal()).getName().equals(teacher.getUsername()))