diff --git a/.github/workflows/build-and-push-tmp.yml b/.github/workflows/build-and-push-tmp.yml new file mode 100644 index 00000000..55d32ea9 --- /dev/null +++ b/.github/workflows/build-and-push-tmp.yml @@ -0,0 +1,92 @@ +# Move to ls1intum/.github/.github/workflows/build-and-push-docker-image.yml@main in the future +name: Build and Push Docker Image + +on: + workflow_call: + inputs: + image-name: + type: string + default: ${{ github.repository }} + description: "The name for the docker image (Default: Repository name)" + docker-file: + type: string + default: Dockerfile + description: "The path to the Dockerfile (Default: ./Dockerfile)" + docker-context: + type: string + default: . + description: "The context for the Docker build (Default: .)" + build-args: + type: string + description: "List of additional build contexts (e.g., name=path)" + required: false + platforms: + type: string + description: "List of platforms for which to build the image" + default: linux/amd64,linux/arm64 + registry: + type: string + default: ghcr.io + description: "The registry to push the image to (Default: ghcr.io)" + + secrets: + registry-user: + required: false + registry-password: + required: false + + outputs: + image-tag: + description: "The tag of the pushed image" + value: ${{ jobs.build.outputs.image-tag }} +jobs: + build: + name: Build Docker Image for ${{ inputs.image-name }} + runs-on: ubuntu-latest + outputs: + image-tag: ${{ steps.set-tag.outputs.image-tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: all + + - name: Install Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ inputs.registry }} + username: ${{ secrets.registry-user || github.actor }} + password: ${{ secrets.registry-password || secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ inputs.registry }}/${{ inputs.image-name }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=branch + type=ref,event=pr + + - name: Set image tag output + id: set-tag + run: echo "::set-output name=image-tag::${{ steps.meta.outputs.version }}" + + - name: Build and push Docker Image + uses: docker/build-push-action@v6 + with: + context: ${{ inputs.docker-context }} + file: ${{ inputs.docker-file }} + platforms: ${{ inputs.platforms }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: ${{ inputs.build-args }} + push: true \ No newline at end of file diff --git a/.github/workflows/build-and-push.yml b/.github/workflows/build-and-push.yml new file mode 100644 index 00000000..f454f3f6 --- /dev/null +++ b/.github/workflows/build-and-push.yml @@ -0,0 +1,18 @@ +name: Build Docker Image + +on: + workflow_call: + outputs: + image-tag: + description: "The tag of the pushed image" + value: ${{ jobs.build-and-push-workflow.outputs.image-tag }} + +jobs: + build-and-push-workflow: + name: Build and Push Docker Image + # uses: ls1intum/.github/.github/workflows/build-and-push-docker-image.yml@main + uses: ./.github/workflows/build-and-push-tmp.yml + with: + image-name: ls1intum/apollon_standalone + docker-file: Dockerfile.redis + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml new file mode 100644 index 00000000..ec9ca1ab --- /dev/null +++ b/.github/workflows/deploy-docker.yml @@ -0,0 +1,103 @@ +name: Deploy Docker Image + +on: + workflow_call: + inputs: + environment: + required: true + type: string + image-name: + type: string + default: ${{ github.repository }} + description: "The name for the docker image (Default: Repository name)" + image-tag: + default: "latest" + type: string + description: "The tag for the docker image (Default: latest)" + +jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: ${{ inputs.environment }} + url: 'https://${{ vars.SERVER_HOST }}' + steps: + - name: SSH to VM and Execute Docker-Compose Down (if exists) + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ vars.VM_HOST }} + username: ${{ vars.VM_USERNAME }} + key: ${{ secrets.VM_SSH_PRIVATE_KEY }} + proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }} + proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }} + proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }} + proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }} + script: | + #!/bin/bash + set -e # Exit immediately if a command exits with a non-zero status + + COMPOSE_FILE="docker-compose.prod.yml" + ENV_FILE=".env.prod" + + # Check if docker-compose.prod.yml exists + if [ -f "$COMPOSE_FILE" ]; then + echo "$COMPOSE_FILE found." + + # Check if .env.prod exists + if [ -f "$ENV_FILE" ]; then + docker compose -f "$COMPOSE_FILE" --env-file="$ENV_FILE" down --remove-orphans --rmi all + else + docker compose -f "$COMPOSE_FILE" down --remove-orphans --rmi all + fi + else + echo "$COMPOSE_FILE does not exist. Skipping docker compose down." + fi + + - name: checkout + uses: actions/checkout@v4 + + - name: Copy Docker Compose File From Repo to VM Host + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ vars.VM_HOST }} + username: ${{ vars.VM_USERNAME }} + key: ${{ secrets.VM_SSH_PRIVATE_KEY }} + proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }} + proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }} + proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }} + proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }} + source: "./docker-compose.prod.yml" + target: /home/${{ vars.VM_USERNAME }} + + - name: SSH to VM and create .env.prod file + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ vars.VM_HOST }} + username: ${{ vars.VM_USERNAME }} + key: ${{ secrets.VM_SSH_PRIVATE_KEY }} + proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }} + proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }} + proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }} + proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }} + script: | + touch .env.prod + + echo "ENVIRONMENT=${{ vars.ENVIRONMENT }}" > .env.prod + + echo "DEPLOYMENT_URL=${{ vars.DEPLOYMENT_URL }}" > .env.prod + echo "APOLLON_REDIS_DIAGRAM_TTL=${{ vars.APOLLON_REDIS_DIAGRAM_TTL }}" >> .env.prod + + echo "IMAGE_TAG=${{ inputs.image-tag }}" >> .env.prod + + - name: SSH to VM and Execute Docker-Compose Up + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ vars.VM_HOST }} + username: ${{ vars.VM_USERNAME }} + key: ${{ secrets.VM_SSH_PRIVATE_KEY }} + proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }} + proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }} + proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }} + proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }} + script: | + docker compose -f docker-compose.prod.yml --env-file=.env.prod up --pull=always -d \ No newline at end of file diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 00000000..734ac014 --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,18 @@ +name: Build and Deploy to Dev + +on: + pull_request: + branches: [main] + +jobs: + build-dev-container: + uses: ./.github/workflows/build-and-push.yml + secrets: inherit + deploy-dev-container: + needs: build-dev-container + uses: ./.github/workflows/deploy-docker.yml + secrets: inherit + with: + environment: Dev + image-name: ls1intum/apollon_standalone + image-tag: "${{ needs.build-dev-container.outputs.image-tag }}" \ No newline at end of file diff --git a/Dockerfile.redis b/Dockerfile.redis index 09d8f44c..bad7eb50 100644 --- a/Dockerfile.redis +++ b/Dockerfile.redis @@ -7,11 +7,9 @@ RUN apk add --no-cache \ pango-dev \ giflib-dev - -ARG DEPLOYMENT_URL="http://localhost:8080" - -ENV APOLLON_REDIS_URL="" -ENV DEPLOYMENT_URL=${DEPLOYMENT_URL} +ENV DEPLOYMENT_URL="http://localhost:8080" +ENV APOLLON_REDIS_URL="redis://localhost:6379" +ENV APOLLON_REDIS_DIAGRAM_TTL="30d" WORKDIR /app diff --git a/README.md b/README.md index 77ff7509..b622c85b 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Apollon Standalone is the Standalone version of the [Apollon Editor](https://github.com/ls1intum/Apollon) There are two variants how you can use this editor: + 1. As web application which only runs in the users environment (modeling functionality). 2. With an application server which enables some extra features, like sharing of diagrams. @@ -20,42 +21,43 @@ All you have to do is go to the [URL](https://apollon.ase.in.tum.de/) and start The user interface of Apollon is simple to use. It works just like any other office and drawing tool that most users are familiar with. -- Select the diagram type you want to draw by clicking on the `File > New` menu. This selection determines the availability of elements that the user can use while drawing their diagram, making it easier for users who are newly introduced to modeling. -- Adding the element is as easy as dragging it from the elements menu and dropping it to the canvas. So is drawing the connection between them, simply drag and connect two or multiple elements. -- The layout of the connection is drawn automatically by the editor. If you want to manually layout it, use the existing waypoints features. -- Edit or style the text or change the colors of any elements by double-clicking on them. An easy-to-use menu will allow you to do so. -- Use keyboard shortcuts to copy, paste, delete and move the elements throughout the canvas. -- Change the theme of the editor by clicking on the dark/light mode switch. +- Select the diagram type you want to draw by clicking on the `File > New` menu. This selection determines the availability of elements that the user can use while drawing their diagram, making it easier for users who are newly introduced to modeling. +- Adding the element is as easy as dragging it from the elements menu and dropping it to the canvas. So is drawing the connection between them, simply drag and connect two or multiple elements. +- The layout of the connection is drawn automatically by the editor. If you want to manually layout it, use the existing waypoints features. +- Edit or style the text or change the colors of any elements by double-clicking on them. An easy-to-use menu will allow you to do so. +- Use keyboard shortcuts to copy, paste, delete and move the elements throughout the canvas. +- Change the theme of the editor by clicking on the dark/light mode switch. ### Import and Export your diagrams Users can easily import the existing Apollon diagram to any editor that uses the Apollon library and continue editing. -![Import Diagram](/docs/images/Import.gif "Import Diagram") +![Import Diagram](/docs/images/Import.gif 'Import Diagram') -Exporting the diagrams is as easy as importing them. +Exporting the diagrams is as easy as importing them. Click on `File > Export` and select the format of the diagram to be exported as. Currently, Apollon standalone supports five different formats: `SVG`, `PNG (White Background)`, `PNG (Transparent Background)`, `JSON`, and `PDF`. -![Export Diagram](/docs/images/Export.png "Export Diagram") +![Export Diagram](/docs/images/Export.png 'Export Diagram') ### Create diagram from template -Users in Apollon Standalone can also create a diagram from a template if they do not want to draw a diagram from scratch. +Users in Apollon Standalone can also create a diagram from a template if they do not want to draw a diagram from scratch. To do that, all they have to do is click on `File > Start from Template` and select one of the templates from the list of available templates. -![Start from Template](/docs/images/StartFromTemplate.gif "Start from Template") +![Start from Template](/docs/images/StartFromTemplate.gif 'Start from Template') ### Share your diagram with others Users can share the diagram in Apollon Standalone in four different types. -- `Edit`: In this mode of sharing, the user will be able to make changes to the shared diagram. -- `Give Feedback`: In this mode of sharing, the user will not be able to make changes to the shared diagram, but can only provide feedback to it. -- `See Feedback`: In this mode of sharing, the user can view feedback provided to the shared diagram. -- `Collaborate`: In this mode of sharing, users joining the collaboration session will be able to work on the diagram collaboratively with other users. +- `Edit`: In this mode of sharing, the user will be able to make changes to the shared diagram. +- `Collaborate`: In this mode of sharing, users joining the collaboration session will be able to work on the diagram collaboratively with other users. +- `Embed`: In this mode of sharing, the user embeds the diagram in a Git issue/pull request. The embedding displays the latest version of the diagram. +- `Give Feedback`: In this mode of sharing, the user will not be able to make changes to the shared diagram, but can only provide feedback to it. +- `See Feedback`: In this mode of sharing, the user can view feedback provided to the shared diagram. -![Real-time collaboration](/docs/images/ShareDialog.png "Real-time collaboration") +![Real-time collaboration](/docs/images/ShareDialog.png 'Real-time collaboration') ### Collaborate in real-time @@ -63,7 +65,7 @@ Apollon Standalone can be used as a collaborative modeling canvas, where multipl Any changes made by one user will be visible throughout the canvas of all other users that are in collaboration sessions in real-time. Active elements that are interacted with by users in a session are highlighted in the canvas. -![Real-time collaboration](/docs/images/RealTimeCollaboration.gif "Real-time collaboration") +![Real-time collaboration](/docs/images/RealTimeCollaboration.gif 'Real-time collaboration') ## Build the application @@ -94,16 +96,17 @@ page application will be loaded. ### Web application + application server There are two variants to set this up: + 1. Manual on a linux vm 2. In a docker container - #### Manual setup (Installation of application server on linux machine) > [!IMPORTANT] > Please make sure if there is any requirements regarding additional dependencies to build the node canvas package for -your operating system! You can find instructions for installing these dependencies here: +> your operating system! You can find instructions for installing these dependencies here: > https://github.com/Automattic/node-canvas#compiling + ``` # clone the repository git clone https://github.com/ls1intum/Apollon_standalone @@ -141,16 +144,17 @@ chown apollon_standalone path/to/diagrams ``` Add the path to the created directory to: + - the cronjob in delete-stale-diagrams.cronjob.txt - in packages/server/src/main/constants.ts #### Install as a service -Configure the apollon_standalone.service file so that the paths +Configure the apollon_standalone.service file so that the paths match the paths to your installation folder ``` -# After adjusting the service file, copy the service file apollon_standalone.service +# After adjusting the service file, copy the service file apollon_standalone.service # into the /etc/systemd/system directory service apollon_standalone start cp apollon_standalone.service /etc/systemd/system/ @@ -158,7 +162,7 @@ cp apollon_standalone.service /etc/systemd/system/ cd path/to/application/build/server chmod +x server.js -# Start the service +# Start the service sudo service apollon_standalone start # Status of the service @@ -166,6 +170,7 @@ service apollon_standalone status ``` Error codes on server start: + - (code=exited, status=217/USER) -> apollon_standalone user does not exist - (code=exited, status=203/USER) -> script not executable @@ -201,7 +206,7 @@ git clone https://github.com/ls1intum/Apollon_standalone # build docker container docker build -t apollon_standalone . -run docker container +run docker container docker run -d --name apollon_standalone -p 8080:8080 apollon_standalone # build the web application and the application server @@ -225,7 +230,6 @@ To use Redis, set the environment variable `APOLLON_REDIS_URL` to the URL of the > [!IMPORTANT] > Apollon Standalone requires the Redis JSON module to be enabled. [Read the documents](https://redis.io/docs/latest/develop/data-types/json/) to learn how to enable the JSON module. - ```bash APOLLON_REDIS_URL=redis://[[username]:[password]@][host][:port] ``` @@ -267,7 +271,7 @@ Add a `.env` file in the root folder of the code. Add the following variables: ```toml # The URL of the server, e.g. the address at which # Apollon Standalone would be accessible after deployment. -DEPLOYMENT_URL=https://my.server/apollon/ +DEPLOYMENT_URL=https://my.server/apollon # The duration for which shared diagrams will be stored # (they will be removed afterwards) @@ -297,7 +301,7 @@ mkdir diagrams # start webpack dev server npm start -# accessible via localhost:8888 (webpack dev server with proxy to application server) +# accessible via localhost:8080 (webpack dev server with proxy to application server) # accesible via localhost:8080 (application server with static files) ``` @@ -313,15 +317,15 @@ npm run update While developing the Standalone project, it is often required to make changes in the Apollon project. This can be achieved by executing the following workflow. -1. In the *Apollon* project: Generate a symlink by executing `npm link` command. -2. In the *Standalone* project: Link the generated symlink of Apollon *(from step 1)* by executing `npm link "@ls1intum/apollon"` command. +1. In the _Apollon_ project: Generate a symlink by executing `npm link` command. +2. In the _Standalone_ project: Link the generated symlink of Apollon _(from step 1)_ by executing `npm link "@ls1intum/apollon"` command. For more information please refer to the [documentation](https://docs.npmjs.com/cli/v9/commands/npm-link) of npm. -> ***Note***: While making changes in the *Apollon* project, for the changes to get reflected in *Standalone*, execute the following workflow: +> **_Note_**: While making changes in the _Apollon_ project, for the changes to get reflected in _Standalone_, execute the following workflow: > -> - Recompile the Apollon project by executing `npm run prepare` -> - Rebuild the Standalone project by executing `npm run build` +> - Recompile the Apollon project by executing `npm run prepare` +> - Rebuild the Standalone project by executing `npm run build` ### Using Redis in Development diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 00000000..54ac0cba --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,36 @@ +version: '3.8' + +services: + redis: + image: redis/redis-stack-server:7.4.0-v1 + container_name: apollon-redis + volumes: + - ./redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 5 + restart: unless-stopped + networks: + - apollon-network + + apollon-standalone: + image: "ghcr.io/ls1intum/apollon_standalone:${IMAGE_TAG}" + container_name: apollon-standalone + environment: + - APOLLON_REDIS_URL=redis://apollon-redis:6379 + - APOLLON_REDIS_DIAGRAM_TTL=${APOLLON_REDIS_DIAGRAM_TTL} + - DEPLOYMENT_URL=${DEPLOYMENT_URL} + restart: unless-stopped + ports: + - "8080:8080" + depends_on: + redis: + condition: service_healthy + networks: + - apollon-network + +networks: + apollon-network: + driver: bridge diff --git a/docs/images/ShareDialog.png b/docs/images/ShareDialog.png index 689a06b4..8a1b057d 100644 Binary files a/docs/images/ShareDialog.png and b/docs/images/ShareDialog.png differ diff --git a/package-lock.json b/package-lock.json index a31bee91..d9eb7b1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4650,6 +4650,13 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/audit-debounce": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/audit-debounce/-/audit-debounce-0.1.1.tgz", @@ -5264,6 +5271,19 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -5714,6 +5734,19 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/cssstyle": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rrweb-cssom": "^0.7.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -5733,6 +5766,20 @@ "node": ">=0.12" } }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -6906,6 +6953,21 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -7152,6 +7214,19 @@ "node": "*" } }, + "node_modules/global-jsdom": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/global-jsdom/-/global-jsdom-25.0.0.tgz", + "integrity": "sha512-Y8dUX6R5Aw5/cutvBY8ofSs2TJyHC3WVGAQGIhCeWlIpKjYcydh3APbxQaeKSfrawVO/YUQ0MAFJfjQDOPVY8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "jsdom": ">=25 <26" + } + }, "node_modules/global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -7412,6 +7487,19 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/html-entities": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", @@ -7539,6 +7627,20 @@ "node": ">=8.0.0" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/http-proxy-middleware": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", @@ -7928,6 +8030,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -8030,6 +8139,47 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -8657,6 +8807,26 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-loader": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-loader/-/node-loader-2.1.0.tgz", + "integrity": "sha512-OwjPkyh8+7jW8DMd/iq71uU1Sspufr/C2+c3t0p08J3CrM9ApZ4U53xuisNrDXOHyGi5OYHgtfmmh+aK9zJA6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.3" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -8714,6 +8884,13 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/nwsapi": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz", + "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -8944,6 +9121,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -10040,6 +10230,13 @@ "rimraf": "bin.js" } }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" + }, "node_modules/run-applescript": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", @@ -10118,6 +10315,19 @@ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "license": "ISC" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -11154,6 +11364,13 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/table": { "version": "6.8.2", "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", @@ -11856,6 +12073,19 @@ "node": ">= 0.8" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", @@ -12346,6 +12576,23 @@ } } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/xmldoc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.3.0.tgz", @@ -12465,6 +12712,9 @@ "@types/node": "22.9.0", "@types/pdfmake": "0.2.9", "copy-webpack-plugin": "12.0.2", + "global-jsdom": "25.0.0", + "jsdom": "25.0.1", + "node-loader": "^2.1.0", "ts-node": "10.9.2", "ts-node-dev": "2.0.0" }, diff --git a/packages/server/package-lock.json b/packages/server/package-lock.json index 509a22cd..47e5af87 100644 --- a/packages/server/package-lock.json +++ b/packages/server/package-lock.json @@ -29,6 +29,9 @@ "@types/node": "22.9.0", "@types/pdfmake": "0.2.9", "copy-webpack-plugin": "12.0.2", + "global-jsdom": "25.0.0", + "jsdom": "25.0.1", + "node-loader": "^2.1.0", "ts-node": "10.9.2", "ts-node-dev": "2.0.0" }, @@ -2113,6 +2116,13 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/audit-debounce": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/audit-debounce/-/audit-debounce-0.1.1.tgz", @@ -2134,6 +2144,16 @@ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", "license": "MIT" }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2414,6 +2434,19 @@ "color-support": "bin.js" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -2534,6 +2567,70 @@ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", "license": "MIT" }, + "node_modules/cssstyle": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rrweb-cssom": "^0.7.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2549,6 +2646,13 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true, + "license": "MIT" + }, "node_modules/decompress-response": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", @@ -2615,6 +2719,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -2695,6 +2809,16 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -2719,6 +2843,19 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -2964,6 +3101,21 @@ "node": ">= 0.8" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3136,6 +3288,19 @@ "license": "BSD-2-Clause", "peer": true }, + "node_modules/global-jsdom": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/global-jsdom/-/global-jsdom-25.0.0.tgz", + "integrity": "sha512-Y8dUX6R5Aw5/cutvBY8ofSs2TJyHC3WVGAQGIhCeWlIpKjYcydh3APbxQaeKSfrawVO/YUQ0MAFJfjQDOPVY8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "jsdom": ">=25 <26" + } + }, "node_modules/globby": { "version": "14.0.2", "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", @@ -3257,6 +3422,19 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -3273,6 +3451,51 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -3462,6 +3685,13 @@ "node": ">=0.12.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -3500,6 +3730,129 @@ "integrity": "sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==", "license": "MIT" }, + "node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -3515,6 +3868,19 @@ "dev": true, "license": "MIT" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -3526,6 +3892,21 @@ "node": ">=6.11.5" } }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -3806,6 +4187,26 @@ } } }, + "node_modules/node-loader": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-loader/-/node-loader-2.1.0.tgz", + "integrity": "sha512-OwjPkyh8+7jW8DMd/iq71uU1Sspufr/C2+c3t0p08J3CrM9ApZ4U53xuisNrDXOHyGi5OYHgtfmmh+aK9zJA6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.3" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -3861,6 +4262,13 @@ "set-blocking": "^2.0.0" } }, + "node_modules/nwsapi": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", + "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3934,6 +4342,19 @@ "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", "license": "MIT" }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4105,7 +4526,6 @@ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -4335,6 +4755,13 @@ "rimraf": "bin.js" } }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4400,6 +4827,19 @@ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "license": "ISC" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/schema-utils": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", @@ -4737,6 +5177,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -4896,6 +5343,26 @@ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", "license": "MIT" }, + "node_modules/tldts": { + "version": "6.1.65", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.65.tgz", + "integrity": "sha512-xU9gLTfAGsADQ2PcWee6Hg8RFAv0DnjMGVJmDnUmI8a9+nYmapMQix4afwrdaCtT+AqP4MaxEzu7cCrYmBPbzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.65" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.65", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.65.tgz", + "integrity": "sha512-Uq5t0N0Oj4nQSbU8wFN1YYENvMthvwU13MQrMJRspYCGLSAZjAfoBOJki5IQpnBM/WFskxxC/gIOTwaedmHaSg==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4918,6 +5385,19 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -5187,6 +5667,19 @@ "node": ">= 0.8" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/watchpack": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", @@ -5324,6 +5817,29 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -5349,6 +5865,45 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "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 + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/xmldoc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.3.0.tgz", diff --git a/packages/server/package.json b/packages/server/package.json index b6180a66..21238374 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -33,6 +33,9 @@ "@types/node": "22.9.0", "@types/pdfmake": "0.2.9", "copy-webpack-plugin": "12.0.2", + "global-jsdom": "25.0.0", + "jsdom": "25.0.1", + "node-loader": "^2.1.0", "ts-node": "10.9.2", "ts-node-dev": "2.0.0" } diff --git a/packages/server/src/main/resources/diagram-resource.ts b/packages/server/src/main/resources/diagram-resource.ts index b827cf91..642cbd01 100644 --- a/packages/server/src/main/resources/diagram-resource.ts +++ b/packages/server/src/main/resources/diagram-resource.ts @@ -4,9 +4,11 @@ import pdfFonts from 'pdfmake/build/vfs_fonts'; import { DiagramDTO } from 'shared'; import { DiagramService } from '../services/diagram-service/diagram-service'; import { DiagramStorageFactory } from '../services/diagram-storage'; +import { ConversionService } from '../services/conversion-service/conversion-service'; export class DiagramResource { diagramService: DiagramService = new DiagramService(DiagramStorageFactory.getStorageService()); + conversionService: ConversionService = new ConversionService(); getDiagram = (req: Request, res: Response) => { const tokenValue: string = req.params.token; @@ -17,12 +19,23 @@ export class DiagramResource { if (/^[a-zA-Z0-9]+$/.test(tokenValue)) { this.diagramService .getDiagramByLink(tokenValue) - .then((diagram: DiagramDTO | undefined) => { + .then(async (diagram: DiagramDTO | undefined) => { if (diagram) { - res.json(diagram); - } else { - res.status(404).send('Diagram not found'); + if (req.query.type === 'svg') { + const diagramSvg = (await this.conversionService.convertToSvg(diagram.model)).svg; + const diagramSvgWhiteBackground = diagramSvg.replace( + /]*)>/, + '', + ); + + res.setHeader('Content-Type', 'image/svg+xml'); + return res.send(diagramSvgWhiteBackground); + } + + return res.json(diagram); } + + return res.status(404).send('Diagram not found'); }) .catch(() => res.status(503).send('Error occurred')); } else { diff --git a/packages/server/src/main/server.ts b/packages/server/src/main/server.ts index 21143e1c..281f32d2 100644 --- a/packages/server/src/main/server.ts +++ b/packages/server/src/main/server.ts @@ -1,3 +1,5 @@ +import fs from 'fs'; +import path from 'path'; import bodyParser from 'body-parser'; import express, { RequestHandler } from 'express'; import * as Sentry from '@sentry/node'; @@ -19,6 +21,15 @@ if (process.env.SENTRY_DSN) { Sentry.setTag('package', 'server'); } +// Replace http://localhost:8080 with the actual process.env.DEPLOYMENT_URL +const jsFiles = fs.readdirSync(webappPath).filter((file) => file.endsWith('.js')); +jsFiles.forEach((file) => { + const filePath = path.join(webappPath, file); + const content = fs.readFileSync(filePath, 'utf8') + .replace(/http:\/\/localhost:8080/g, process.env.DEPLOYMENT_URL || 'http://localhost:8080'); + fs.writeFileSync(filePath, content); +}); + app.use('/', express.static(webappPath)); app.use(bodyParser.json() as RequestHandler); app.use( diff --git a/packages/server/src/main/services/conversion-service/conversion-service.ts b/packages/server/src/main/services/conversion-service/conversion-service.ts new file mode 100644 index 00000000..f98e4203 --- /dev/null +++ b/packages/server/src/main/services/conversion-service/conversion-service.ts @@ -0,0 +1,22 @@ +import 'global-jsdom/register'; +import { ApollonEditor, SVG, UMLModel } from '@ls1intum/apollon'; + +export class ConversionService { + convertToSvg = async (model: UMLModel): Promise => { + document.body.innerHTML = '
'; + // JSDOM does not support getBBox so we have to mock it here + // @ts-ignore + window.SVGElement.prototype.getBBox = () => ({ + x: 0, + y: 0, + width: 10, + height: 10, + }); + const container = document.querySelector('div')!; + const editor = new ApollonEditor(container, {}); + await editor.nextRender; + editor.model = model; + await editor.nextRender; + return editor.exportAsSVG(); + }; +} diff --git a/packages/server/webpack/webpack.config.js b/packages/server/webpack/webpack.config.js index 0df81f0b..bc38332b 100644 --- a/packages/server/webpack/webpack.config.js +++ b/packages/server/webpack/webpack.config.js @@ -22,6 +22,10 @@ module.exports = { use: 'ts-loader', test: /\.ts?$/, }, + { + use: 'node-loader', + test: /\.node$/, + }, ], }, externals: { diff --git a/packages/shared/src/diagram-view.ts b/packages/shared/src/diagram-view.ts index 6d2b5e12..945a483f 100644 --- a/packages/shared/src/diagram-view.ts +++ b/packages/shared/src/diagram-view.ts @@ -3,4 +3,5 @@ export enum DiagramView { SEE_FEEDBACK = 'SEE_FEEDBACK', EDIT = 'EDIT', COLLABORATE = 'COLLABORATE', + EMBED = 'EMBED', } diff --git a/packages/webapp/src/main/components/modals/create-version-modal/create-version-modal.tsx b/packages/webapp/src/main/components/modals/create-version-modal/create-version-modal.tsx index bb6d4f67..a8224329 100644 --- a/packages/webapp/src/main/components/modals/create-version-modal/create-version-modal.tsx +++ b/packages/webapp/src/main/components/modals/create-version-modal/create-version-modal.tsx @@ -4,7 +4,7 @@ import { ModalContentProps } from '../application-modal-types'; import { toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; -import { selectDiagram, updateDiagramThunk } from '../../../services/diagram/diagramSlice'; +import { selectDiagram, setCreateNewEditor, updateDiagramThunk } from '../../../services/diagram/diagramSlice'; import { LocalStorageRepository } from '../../../services/local-storage/local-storage-repository'; import { displayError } from '../../../services/error-management/errorManagementSlice'; import { DiagramRepository } from '../../../services/diagram/diagram-repository'; @@ -42,6 +42,7 @@ export const CreateVersionModal: React.FC = ({ close }) => { DiagramRepository.publishDiagramVersionOnServer(diagramCopy, token) .then((res) => { dispatch(updateDiagramThunk(res.diagram)); + dispatch(setCreateNewEditor(true)); dispatch(setDisplayUnpublishedVersion(false)); LocalStorageRepository.setLastPublishedToken(res.diagramToken); displayToast(); diff --git a/packages/webapp/src/main/components/modals/share-modal/share-modal.tsx b/packages/webapp/src/main/components/modals/share-modal/share-modal.tsx index 87fbb57b..01489b37 100644 --- a/packages/webapp/src/main/components/modals/share-modal/share-modal.tsx +++ b/packages/webapp/src/main/components/modals/share-modal/share-modal.tsx @@ -11,7 +11,11 @@ import { InfoCircle } from 'react-bootstrap-icons'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { displayError } from '../../../services/error-management/errorManagementSlice'; import { useNavigate } from 'react-router-dom'; -import { setDisplayUnpublishedVersion, updateDiagramThunk } from '../../../services/diagram/diagramSlice'; +import { + setCreateNewEditor, + setDisplayUnpublishedVersion, + updateDiagramThunk, +} from '../../../services/diagram/diagramSlice'; import { selectDisplaySidebar, toggleSidebar } from '../../../services/version-management/versionManagementSlice'; export const ShareModal: React.FC = ({ close }) => { @@ -23,6 +27,15 @@ export const ShareModal: React.FC = ({ close }) => { const tokenInUrl = urlPath.substring(1); // This removes the leading "/" const getLinkForView = (token?: string) => { + if (LocalStorageRepository.getLastPublishedType() === DiagramView.EMBED) { + return ( + `![${ + diagram ? diagram.title : 'Diagram' + }](${DEPLOYMENT_URL}/api/diagrams/${LocalStorageRepository.getLastPublishedToken()}?type=svg)` + + `\n[Edit a copy](${DEPLOYMENT_URL}/${LocalStorageRepository.getLastPublishedToken()}?view=${DiagramView.EDIT})` + ); + } + return `${DEPLOYMENT_URL}/${token || LocalStorageRepository.getLastPublishedToken()}?view=${LocalStorageRepository.getLastPublishedType()}`; }; @@ -36,6 +49,9 @@ export const ShareModal: React.FC = ({ close }) => { case DiagramView.COLLABORATE: return 'collaborate'; + + case DiagramView.EMBED: + return 'embed'; } return 'edit'; }; @@ -99,6 +115,7 @@ export const ShareModal: React.FC = ({ close }) => { DiagramRepository.publishDiagramVersionOnServer(diagramCopy, diagram.token) .then((res) => { dispatch(updateDiagramThunk(res.diagram)); + dispatch(setCreateNewEditor(true)); dispatch(setDisplayUnpublishedVersion(false)); token = res.diagramToken; }) @@ -140,7 +157,7 @@ export const ShareModal: React.FC = ({ close }) => {
-
+
-
+
-
+
+
+
+
-
+
@@ -191,9 +219,13 @@ export const ShareModal: React.FC = ({ close }) => {
Recently shared Diagram: - +
diff --git a/packages/webapp/webpack/webpack.common.js b/packages/webapp/webpack/webpack.common.js index 875fdfc2..14c8b68c 100644 --- a/packages/webapp/webpack/webpack.common.js +++ b/packages/webapp/webpack/webpack.common.js @@ -77,7 +77,7 @@ module.exports = { }), new webpack.DefinePlugin({ 'process.env.APPLICATION_SERVER_VERSION': JSON.stringify(process.env.APPLICATION_SERVER_VERSION || true), - 'process.env.DEPLOYMENT_URL': JSON.stringify(process.env.DEPLOYMENT_URL || 'http://localhost:8888'), + 'process.env.DEPLOYMENT_URL': JSON.stringify(process.env.DEPLOYMENT_URL || 'http://localhost:8080'), 'process.env.SENTRY_DSN': JSON.stringify(process.env.SENTRY_DSN || null), 'process.env.POSTHOG_HOST': JSON.stringify(process.env.POSTHOG_HOST || null), 'process.env.POSTHOG_KEY': JSON.stringify(process.env.POSTHOG_KEY || null), diff --git a/packages/webapp/webpack/webpack.dev.js b/packages/webapp/webpack/webpack.dev.js index e72e66cb..ea22ad23 100644 --- a/packages/webapp/webpack/webpack.dev.js +++ b/packages/webapp/webpack/webpack.dev.js @@ -17,7 +17,7 @@ module.exports = merge(common, { devServer: { static: path.join(__dirname, '../../build/webapp'), host: '0.0.0.0', - port: 8888, + port: 8080, proxy: [ { context: ['/'],