diff --git a/.github/workflows/clean_packages.yml b/.github/workflows/clean_packages.yml new file mode 100644 index 0000000000..ace4eddbd6 --- /dev/null +++ b/.github/workflows/clean_packages.yml @@ -0,0 +1,29 @@ +--- +name: Remove old unused scrumlr packages + +on: + schedule: + - cron: "0 7 * * 1-5" # every monday through friday at 7 am + +jobs: + clean_scrumlr_server_packages: + name: "clean scrumlr server packages" + runs-on: ubuntu-latest + steps: + - uses: actions/delete-package-versions@v5 + with: + package-name: "scrumlr.io/scrumlr-server" + package-type: "container" + min-versions-to-keep: 256 + delete-only-untagged-versions: "true" + + clean_scrumlr_frontend_packages: + name: "clean scrumlr frontend packages" + runs-on: ubuntu-latest + steps: + - uses: actions/delete-package-versions@v5 + with: + package-name: "scrumlr.io/scrumlr-frontend" + package-type: "container" + min-versions-to-keep: 256 + delete-only-untagged-versions: "true" diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 6576b0aefb..765f0a589f 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -29,7 +29,7 @@ jobs: - name: Get yarn cache directory path id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - uses: actions/cache@v4 id: yarn-cache @@ -85,7 +85,8 @@ jobs: - name: Go lint uses: golangci/golangci-lint-action@v4 with: - version: v1.54 + version: v1.61 + skip-pkg-cache: true working-directory: ./server/src/ - name: Build server @@ -96,19 +97,19 @@ jobs: package: needs: [test-frontend, test-backend] - uses: inovex/scrumlr.io/.github/workflows/package.yml@main + uses: ./.github/workflows/package.yml deploy_to_dev_cluster: needs: package if: ${{ github.event_name == 'pull_request' && github.event.pull_request.user.login != 'dependabot[bot]' || github.ref_name == 'main' }} - uses: inovex/scrumlr.io/.github/workflows/deploy_to_dev_cluster.yml@main + uses: ./.github/workflows/deploy_to_dev_cluster.yml secrets: inherit with: frontend_image_tag: ${{ needs.package.outputs.frontend-image-tag }} server_image_tag: ${{ needs.package.outputs.server-image-tag }} docs_changes: - if : ${{ github.ref == 'refs/heads/main' }} + if : ${{ github.event_name == 'pull_request' && github.event.pull_request.user.login != 'dependabot[bot]' || github.ref_name == 'main' }} runs-on: ubuntu-latest steps: - name: checkout repository @@ -129,7 +130,7 @@ jobs: deploy_github_pages: needs: docs_changes if: ${{ needs.docs_changes.outputs.docs_changed == 'true' }} - uses: inovex/scrumlr.io/.github/workflows/deploy_docs.yml@main + uses: ./.github/workflows/deploy_docs.yml secrets: inherit deployment_health_check: needs: deploy_to_dev_cluster diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml index c05e8830fb..9f2e4b4644 100644 --- a/.github/workflows/deploy_docs.yml +++ b/.github/workflows/deploy_docs.yml @@ -26,6 +26,7 @@ jobs: deploy: needs: build runs-on: ubuntu-latest + if: ${{ github.ref_name == 'main' }} environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} diff --git a/.github/workflows/deploy_to_dev_cluster.yml b/.github/workflows/deploy_to_dev_cluster.yml index 0a7bac41d9..3e73f8cc3f 100644 --- a/.github/workflows/deploy_to_dev_cluster.yml +++ b/.github/workflows/deploy_to_dev_cluster.yml @@ -23,17 +23,17 @@ jobs: pull-requests: write steps: - name: Install kubectl - uses: azure/setup-kubectl@v3 + uses: azure/setup-kubectl@v4 id: install - name: Set up Kubernetes Auth - uses: azure/k8s-set-context@v3 + uses: azure/k8s-set-context@v4 with: method: service-account k8s-secret: ${{ secrets.KUBERNETES_SECRET }} k8s-url: https://kubernetes.default.svc - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Replace template variables run: | @@ -50,7 +50,7 @@ jobs: cat k8s/deployment.yaml - name: Deploy to dev cluster - uses: Azure/k8s-deploy@v4 + uses: Azure/k8s-deploy@v5 with: namespace: scrumlr-dev-deployments force: true diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 2bd3e3c864..675d3b85e5 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -68,7 +68,7 @@ jobs: org.opencontainers.image.description=The web client for scrumlr.io - name: Build and push frontend images - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile @@ -130,7 +130,7 @@ jobs: org.opencontainers.image.description=The server for scrumlr.io - name: Build and push server images - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: context: ./server/src file: ./server/src/Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dbf87220e5..2c8dd59d80 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,6 @@ on: jobs: package: - uses: inovex/scrumlr.io/.github/workflows/package.yml@main + uses: ./.github/workflows/package.yml diff --git a/Dockerfile b/Dockerfile index 238689b45b..5ad946d6b0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,10 @@ -FROM node:lts-hydrogen as build-stage +FROM node:iron-alpine AS build-stage WORKDIR /usr/src/app COPY package.json yarn.lock ./ -RUN yarn install + +RUN yarn install --network-timeout 240000 COPY src/ src/ COPY public/ public/ @@ -15,7 +16,7 @@ COPY .env . RUN yarn build -FROM nginxinc/nginx-unprivileged:1.21-alpine +FROM nginxinc/nginx-unprivileged:1.27-alpine # Toggle visibility of cookie policy, privacy policy, and terms & conditions ENV SCRUMLR_SHOW_LEGAL_DOCUMENTS='' @@ -32,6 +33,7 @@ ENV SCRUMLR_LISTEN_PORT='8080' # Analytics variables ENV SCRUMLR_ANALYTICS_DATA_DOMAIN='' ENV SCRUMLR_ANALYTICS_SRC='' +ENV SCRUMLR_CLARITY_ID='' COPY ./nginx.conf /etc/nginx/templates/scrumlr.io.conf.template COPY --from=build-stage /usr/src/app/build /usr/share/nginx/html diff --git a/deployment/.gitignore b/deployment/.gitignore new file mode 100644 index 0000000000..220dd2c533 --- /dev/null +++ b/deployment/.gitignore @@ -0,0 +1,2 @@ +.env +jwt.key \ No newline at end of file diff --git a/deployment/SKE/create_db_job.yaml b/deployment/SKE/create_db_job.yaml index e2116c6b8f..e05bbf29fd 100644 --- a/deployment/SKE/create_db_job.yaml +++ b/deployment/SKE/create_db_job.yaml @@ -8,7 +8,7 @@ spec: spec: containers: - name: create-db - image: postgres:16.3-alpine + image: postgres:16.4-alpine env: - name: DB_URL value: "$DB_URL" diff --git a/deployment/docker/.env.example b/deployment/docker/.env.example index 1d8ff5e8bb..e3f447c404 100644 --- a/deployment/docker/.env.example +++ b/deployment/docker/.env.example @@ -5,6 +5,7 @@ ANALYTICS_DATA_DOMAIN= ANALYTICS_SRC= # Auth Providers +AUTH_CALLBACK_HOST= APPLE_CLIENT_ID= APPLE_CLIENT_SECRET= AZURE_AD_CLIENT_ID= @@ -16,6 +17,11 @@ GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= MICROSOFT_CLIENT_ID= MICROSOFT_CLIENT_SECRET= +OIDC_CLIENT_ID= +OIDC_CLIENT_SECRET= +OIDC_DISCOVERY_URL=http://oidc.localhost:5556/dex/.well-known/openid-configuration +# Session Secret +SESSION_SECRET= # Your Postgres Password POSTGRES_PASSWORD= # Redis if you use it instead of NATS @@ -35,10 +41,11 @@ SCRUMLR_SERVER_NATS_URL="nats://nats:4222" # Server Port SCRUMLR_SERVER_PORT=8080 # Server URL -SCRUMLR_SERVER_URL="http://localhost:8080/api" +SCRUMLR_SERVER_URL="/api" # Show Legal Documents (Cookie Policy, Privacy Policy, Terms of Service) SCRUMLR_SHOW_LEGAL_DOCUMENTS=true # Websocket URL (wss:// or ws://) -SCRUMLR_WEBSOCKET_URL="ws://localhost:8080/api" +# SCRUMLR_WEBSOCKET_URL="ws://localhost:8080/api" # Webhook URL for feedback WEBHOOK_URL= +SCRUMLR_CLARITY_ID= diff --git a/deployment/docker/Caddyfile b/deployment/docker/Caddyfile new file mode 100644 index 0000000000..852db0e833 --- /dev/null +++ b/deployment/docker/Caddyfile @@ -0,0 +1,14 @@ +0.0.0.0:80 { + log { + output stdout + } + @api { + path /api* + } + reverse_proxy @api scrumlr-backend:8080 + + @frontend { + path / /static* /locales* /login* /board* /new* /timer_finished.mp3 /hotkeys.pdf /legal/* /manifest.json /service-worker.js + } + reverse_proxy @frontend scrumlr-frontend:8080 +} diff --git a/deployment/docker/dex.yaml b/deployment/docker/dex.yaml new file mode 100644 index 0000000000..15e2795c33 --- /dev/null +++ b/deployment/docker/dex.yaml @@ -0,0 +1,26 @@ +--- + +issuer: 'http://oidc.localhost:5556/dex' + +storage: + type: memory + +web: + http: '0.0.0.0:5556' + +staticClients: + - id: '' + redirectURIs: + - 'http://localhost:8080/api/login/oidc/callback' + name: '' + secret: '' + +enablePasswordDB: true +staticPasswords: + - email: '' + # bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2) + hash: '' + username: '' + userID: '' + + diff --git a/deployment/docker/docker-compose.yml b/deployment/docker/docker-compose.yml index 3e63c554d5..f8743aec11 100644 --- a/deployment/docker/docker-compose.yml +++ b/deployment/docker/docker-compose.yml @@ -1,11 +1,9 @@ -version: '3.8' +version: "3.8" services: scrumlr-backend: restart: always - build: - context: ../server/src/ - dockerfile: Dockerfile + image: ghcr.io/inovex/scrumlr.io/scrumlr-server:3.10.1 command: - "/app/main" - "-disable-check-origin" @@ -21,6 +19,7 @@ services: SCRUMLR_FEEDBACK_WEBHOOK_URL: "${WEBHOOK_URL}" SCRUMLR_BASE_PATH: "${SCRUMLR_BASE_PATH}" SCRUMLR_INSECURE: "${SCRUMLR_INSECURE}" + SCRUMLR_AUTH_CALLBACK_HOST: "${AUTH_CALLBACK_HOST}" SCRUMLR_AUTH_GITHUB_CLIENT_ID: "${GITHUB_CLIENT_ID}" SCRUMLR_AUTH_GITHUB_CLIENT_SECRET: "${GITHUB_CLIENT_SECRET}" SCRUMLR_AUTH_AZURE_AD_TENANT_ID: "${AZURE_AD_TENANT_ID}" @@ -28,6 +27,10 @@ services: SCRUMLR_AUTH_AZURE_AD_CLIENT_SECRET: "${AZURE_AD_CLIENT_SECRET}" SCRUMLR_AUTH_APPLE_CLIENT_ID: "${APPLE_CLIENT_ID}" SCRUMLR_AUTH_APPLE_CLIENT_SECRET: "${APPLE_CLIENT_SECRET}" + SCRUMLR_AUTH_OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}" + SCRUMLR_AUTH_OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET}" + SCRUMLR_AUTH_OIDC_DISCOVERY_URL: "${OIDC_DISCOVERY_URL}" + SESSION_SECRET: "${SESSION_SECRET}" # SCRUMLR_CONFIG_PATH: "${SCRUMRL_CONFIG_PATH}" # Redis variables (if you decide to use Redis instead of NATS) SCRUMLR_SERVER_REDIS_HOST: "${REDIS_HOST}" @@ -41,23 +44,22 @@ services: scrumlr-frontend: restart: always - build: - context: ../. - dockerfile: Dockerfile + image: ghcr.io/inovex/scrumlr.io/scrumlr-frontend:3.10.1 environment: SCRUMLR_SERVER_URL: "${SCRUMLR_SERVER_URL}" SCRUMLR_WEBSOCKET_URL: "${SCRUMLR_WEBSOCKET_URL}" SCRUMLR_SHOW_LEGAL_DOCUMENTS: "${SCRUMLR_SHOW_LEGAL_DOCUMENTS}" # Add missing frontend environment variables here - SCRUMLR_LISTEN_PORT: "${SCRUMLR_LISTEN_PORT}" + SCRUMLR_LISTEN_PORT: "${SCRUMLR_LISTEN_PORT:-8080}" SCRUMLR_ANALYTICS_DATA_DOMAIN: "${ANALYTICS_DATA_DOMAIN}" SCRUMLR_ANALYTICS_SRC: "${ANALYTICS_SRC}" + SCRUMLR_CLARITY_ID: "${SCRUMLR_CLARITY_ID}" ports: - "9090:8080" postgres: restart: always - image: postgres:16.3 + image: postgres:16.4 environment: POSTGRES_DB: scrumlr POSTGRES_USER: scrumlr @@ -74,5 +76,25 @@ services: - "4222:4222" - "8222:8222" + # oidc.localhost: + # restart: always + # image: ghcr.io/dexidp/dex:v2.41.1-distroless + # volumes: + # - ./dex.yaml:/etc/dex/config.docker.yaml:ro + # ports: + # - "5556:5556" + # profiles: + # - oidc + + caddy: + image: caddy + restart: always + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile + ports: + - "80:80" + - "443:443" + + volumes: postgres_data: diff --git a/docs/package.json b/docs/package.json index bc55741ca9..21a4259d34 100644 --- a/docs/package.json +++ b/docs/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@astrojs/starlight": "^0.21.5", - "astro": "^4.3.5", + "astro": "^4.16.18", "sharp": "^0.32.5", "@astrojs/check": "^0.5.10", "typescript": "^5.4.5" diff --git a/docs/src/content/docs/dev/contributing.md b/docs/src/content/docs/dev/contributing.md index 49d706a55c..4c6d405085 100644 --- a/docs/src/content/docs/dev/contributing.md +++ b/docs/src/content/docs/dev/contributing.md @@ -48,6 +48,7 @@ $ docker compose --project-directory server/ --profile dev up -d $ go run . -d "postgres://admin:supersecret@localhost:5432/scrumlr?sslmode=disable" -v --disable-check-origin --insecure ``` ## Testing +### Frontend Testing To run the tests locally, you can use the following command in your terminal: ```bash yarn test @@ -55,6 +56,18 @@ yarn test This command will execute the test suite and provide you with detailed feedback on the test results, including any failures or errors encountered. Running tests locally helps you verify that your changes have not introduced any regressions and ensures that the existing functionality remains intact. +### Backend Testing +To ensure the functionality of your backend, you can run tests locally using the following methods: +To test your general Go code, use the built-in testing tool: +```bash +go test ./... -cover -coverprofile=coverage.txt +``` +### API Testing +When all backend components are running, you can use Postman tests to check the API. The CLI tool for this is `newman`, which needs to be installed beforehand: +```bash +newman run api.postman_collection.json --env-var "base_url=localhost:8080" --verbose +``` + In addition to local testing, we utilize GitHub Actions to automate the testing process for every pull request. GitHub Actions automatically runs the test suite against the proposed changes, providing a clear indication of whether the tests pass or not. This ensures that all tests are passing before merging any changes into the main branch, maintaining the stability and integrity of our codebase. We strongly encourage contributors to write new tests when introducing new features or modifying existing functionality. These tests help validate the behavior and correctness of the code, making it easier to identify and fix issues early on. diff --git a/docs/src/content/docs/self-hosting/docker.md b/docs/src/content/docs/self-hosting/docker.md index 47a3cb46e7..c0a7cc5542 100644 --- a/docs/src/content/docs/self-hosting/docker.md +++ b/docs/src/content/docs/self-hosting/docker.md @@ -12,7 +12,7 @@ We maintain a Docker Compose file in our Repository that you can use to deploy S Clone the Scrumlr repository to your server and navigate to the deployment directory. ```sh git clone https://github.com/inovex/scrumlr.io -cd scrumlr.io/deployment/ +cd scrumlr.io/deployment/docker ``` Copy the `.env.example` file to `.env` and adjust the variables to your needs. @@ -20,12 +20,14 @@ Copy the `.env.example` file to `.env` and adjust the variables to your needs. cp .env.example .env ``` +For a new deployment the mandatory variables to fill out are `POSTGRES_PASSWORD` and `SCRUMLR_PRIVATE_KEY`. + ## Generating needed secrets ### Postgres Password -Generate a secure password for the Postgres database. -Make sure to set the `POSTGRES_PASSWORD`variable in your .env file to the generated password. +Make sure to set the `POSTGRES_PASSWORD` variable in your `.env` file to a secure password. For example you can generate a 64 characters long one from the terminal with the following command (if you have `pwgen` installed): + ```sh pwgen -s 64 1 ``` @@ -35,13 +37,50 @@ We use an ECDSA private key to sign the JWT tokens. ```sh openssl ecparam -genkey -name secp521r1 -noout -out jwt.key ``` -Now we need to encode this key to be able to use it as a string in the .env file. +Now we need to encode this key to be able to use it as a string in the `.env` file: ```sh cat jwt.key | awk '{printf "%s\\n", $0}' ``` +Copy the result of this command and paste it into your `.env` file (with `\n` line breaks included) like this, surrounded with quotes: + +```ini +SCRUMLR_PRIVATE_KEY="-----BEGIN EC PRIVATE KEY-----\n...\n-----END EC PRIVATE KEY-----\n" +``` + +### Session Secret + +Make sure to set the `SESSION_SECRET` variable in your `.env` file if you are using an authentication provider. +You can generate a session secret with + +```sh +pwgen -s 64 1 +``` + ## Deployment You can now start the deployment using the following command. ```sh docker-compose up -d ``` + +After a few seconds you can check with `docker ps --all` to see if all the containers have started up. If one crashed or if there is an issue you can check logs with `docker logs (container name or id)` + +## Reverse Proxy +We strongly recommend using a reverse proxy to handle TLS termination and to provide a secure connection to your users. +Scrumlr should work with all major reverse proxies like [Nginx](https://nginx.org), [Traefik](https://traefik.io/traefik/) or [Caddy](https://caddyserver.com/docs/quick-starts/reverse-proxy). +We automatically include a caddy deployment in the docker-compose file, which you can use as a reverse proxy. +All you need to do is updating the `Caddyfile` to include your host domain instead of `0.0.0.0:80`. +If you don't want TLS you can simply keep the specified port. +Keep in mind that running Scrumlr without TLS is **not recommended**. + +``` +your_domain { +} +``` + +## Troubleshooting + +### Scrumlr works fine on my machine, but others get an error when they click on "Start now" + +Make sure the `SCRUMLR_SERVER_URL` in the `.env` file uses your external ip address, instead of `localhost` or `127.0.0.1`. +You can search "what is my ip" on the internet to find your external ip address. diff --git a/docs/src/content/docs/self-hosting/env-vars.md b/docs/src/content/docs/self-hosting/env-vars.md index 2cf735d709..45f636f8f4 100644 --- a/docs/src/content/docs/self-hosting/env-vars.md +++ b/docs/src/content/docs/self-hosting/env-vars.md @@ -39,6 +39,12 @@ SCRUMLR_ANALYTICS_DATA_DOMAIN='' SCRUMLR_ANALYTICS_SRC='' ``` +### Clarity id +The clarity id to use [Clarity](https://clarity.microsoft.com/). +```bash +SCRUMLR_CLARITY_ID='' +``` + ## Backend ### Server Port @@ -92,6 +98,22 @@ The base path of the API. The default is `/`. SCRUMLR_BASE_PATH='' ``` +### Disable Anonymous Login +If set to `true`, users won't be able to log in anonymously, forcing them to use a provider (any OAuth or OIDC). +Note that if this is set to `true`, and no valid providers are available, login won't be possible at all. +Default is `false`. +```bash +SCRUMLR_DISABLE_ANONYMOUS_LOGIN=false +``` + +### Enable Experimental File System Store +Enables an experimental file store for session cookies, which is used during OAuth authentication to store session info while on the provider page. +Required for some OIDC providers, since their session cookies exceed the size limit of 4KB. +Default is `false`. +```bash +SCRUMLR_ENABLE_EXPERIMENTAL_AUTH_FILE_SYSTEM_STORE=false +``` + ### Auth Callback Host The host to which the OAuth callback should redirect. ```bash @@ -135,8 +157,27 @@ SCRUMLR_AUTH_AZURE_AD_CLIENT_SECRET='' Required Apple OAuth credentials. Only configure if you wish to use Apple OAuth. ```bash -SCRUMLR_AUTH_APPLE_CLIENT_ID -SCRUMLR_AUTH_APPLE_CLIENT_SECRET +SCRUMLR_AUTH_APPLE_CLIENT_ID='' +SCRUMLR_AUTH_APPLE_CLIENT_SECRET='' +``` + +### OpenID Connect OAuth +Required OIDC credentials. +Only configure if you wish to use generic OpenID Connect Authentication. +```bash +SCRUMLR_AUTH_OIDC_CLIENT_ID='' +SCRUMLR_AUTH_OIDC_CLIENT_SECRET='' +SCRUMLR_AUTH_OIDC_DISCOVERY_URL='' +SCRUMLR_AUTH_OIDC_USER_IDENT_SCOPE='' +SCRUMLR_AUTH_OIDC_USER_NAME_SCOPE='' +``` +Note: Might require larger session store to be active, see [SCRUMLR_ENABLE_EXPERIMENTAL_AUTH_FILE_SYSTEM_STORE](#enable-experimental-file-system-store) + +### Session Secret +The secret for the session. This secret is used by gothic. +This needs to be configured if you are using an authentication provider. +```bash +SESSION_SECRET='' ``` ### Feedback Webhook URL @@ -149,6 +190,6 @@ SCRUMLR_FEEDBACK_WEBHOOK_URL='' ### Scrumlr Config Path The path to the Scrumlr configuration file. ```bash -SCRUMLR_CONFIG_PATH +SCRUMLR_CONFIG_PATH='' ``` diff --git a/docs/yarn.lock b/docs/yarn.lock index fe066ea684..390e06828f 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -21,15 +21,15 @@ kleur "^4.1.5" yargs "^17.7.2" -"@astrojs/compiler@^2.7.0", "@astrojs/compiler@^2.7.1": - version "2.7.1" - resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-2.7.1.tgz#a7ab59a1c87b9e6e9c6adb34bb2ca3aaa8b9d14c" - integrity sha512-/POejAYuj8WEw7ZI0J8JBvevjfp9jQ9Wmu/Bg52RiNwGXkMV7JnYpsenVfHvvf1G7R5sXHGKlTcxlQWhoUTiGQ== +"@astrojs/compiler@^2.10.3", "@astrojs/compiler@^2.7.0": + version "2.10.3" + resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-2.10.3.tgz#852386445029f7765a70b4c1d1140e175e1d8c27" + integrity sha512-bL/O7YBxsFt55YHU021oL+xz+B/9HvGNId3F9xURN16aeqDK9juHGktdkCSXz+U4nqFACq6ZFvWomOzhV+zfPw== -"@astrojs/internal-helpers@0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@astrojs/internal-helpers/-/internal-helpers-0.4.0.tgz#4fd45cee40b8278c77ed64167970c401ccfe792b" - integrity sha512-6B13lz5n6BrbTqCTwhXjJXuR1sqiX/H6rTxzlXx+lN1NnV4jgnq/KJldCQaUWJzPL5SiWahQyinxAbxQtwgPHA== +"@astrojs/internal-helpers@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@astrojs/internal-helpers/-/internal-helpers-0.4.1.tgz#ceb5de49346dbdbfb6cba1b683c07fef7df56e1c" + integrity sha512-bMf9jFihO8YP940uD70SI/RDzIhUHJAolWVcO1v5PUivxGKvfLZTLTVVxEYzGYyPsA3ivdLNqMnL5VgmQySa+g== "@astrojs/language-server@^2.8.4": version "2.8.4" @@ -77,6 +77,30 @@ unist-util-visit-parents "^6.0.0" vfile "^6.0.1" +"@astrojs/markdown-remark@5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@astrojs/markdown-remark/-/markdown-remark-5.3.0.tgz#fd1f8874f2bd1e2c33a7447d069fc75005b677f2" + integrity sha512-r0Ikqr0e6ozPb5bvhup1qdWnSPUvQu6tub4ZLYaKyG50BXZ0ej6FhGz3GpChKpH7kglRFPObJd/bDyf2VM9pkg== + dependencies: + "@astrojs/prism" "3.1.0" + github-slugger "^2.0.0" + hast-util-from-html "^2.0.3" + hast-util-to-text "^4.0.2" + import-meta-resolve "^4.1.0" + mdast-util-definitions "^6.0.0" + rehype-raw "^7.0.0" + rehype-stringify "^10.0.1" + remark-gfm "^4.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.1.1" + remark-smartypants "^3.0.2" + shiki "^1.22.0" + unified "^11.0.5" + unist-util-remove-position "^5.0.0" + unist-util-visit "^5.0.0" + unist-util-visit-parents "^6.0.1" + vfile "^6.0.3" + "@astrojs/mdx@^2.1.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@astrojs/mdx/-/mdx-2.3.1.tgz#638e28c29f502de095e7fa4a46fd674be05c0a88" @@ -98,7 +122,7 @@ unist-util-visit "^5.0.0" vfile "^6.0.1" -"@astrojs/prism@^3.1.0": +"@astrojs/prism@3.1.0", "@astrojs/prism@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@astrojs/prism/-/prism-3.1.0.tgz#1b70432e0b16fafda191ce780c2820822a55bc46" integrity sha512-Z9IYjuXSArkAUx3N6xj6+Bnvx8OdUSHA8YoOgyepp3+zJmtVYJIl/I18GozdJVW1p5u/CNpl3Km7/gwTJK85cw== @@ -151,215 +175,181 @@ is-wsl "^3.0.0" which-pm-runs "^1.1.0" -"@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" - integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== +"@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== dependencies: - "@babel/highlight" "^7.24.2" + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/compat-data@^7.23.5": - version "7.24.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" - integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== +"@babel/compat-data@^7.25.9": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.3.tgz#99488264a56b2aded63983abd6a417f03b92ed02" + integrity sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g== -"@babel/core@^7.24.3": - version "7.24.4" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.4.tgz#1f758428e88e0d8c563874741bc4ffc4f71a4717" - integrity sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg== +"@babel/core@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" + integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.2" - "@babel/generator" "^7.24.4" - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-module-transforms" "^7.23.3" - "@babel/helpers" "^7.24.4" - "@babel/parser" "^7.24.4" - "@babel/template" "^7.24.0" - "@babel/traverse" "^7.24.1" - "@babel/types" "^7.24.0" + "@babel/code-frame" "^7.26.0" + "@babel/generator" "^7.26.0" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.0" + "@babel/parser" "^7.26.0" + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.26.0" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.23.3", "@babel/generator@^7.24.1", "@babel/generator@^7.24.4": - version "7.24.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.4.tgz#1fc55532b88adf952025d5d2d1e71f946cb1c498" - integrity sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw== +"@babel/generator@^7.26.0", "@babel/generator@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.3.tgz#ab8d4360544a425c90c248df7059881f4b2ce019" + integrity sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ== dependencies: - "@babel/types" "^7.24.0" + "@babel/parser" "^7.26.3" + "@babel/types" "^7.26.3" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" + jsesc "^3.0.2" -"@babel/helper-annotate-as-pure@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" - integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== +"@babel/helper-annotate-as-pure@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" + integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.25.9" -"@babel/helper-compilation-targets@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" - integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== +"@babel/helper-compilation-targets@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875" + integrity sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ== dependencies: - "@babel/compat-data" "^7.23.5" - "@babel/helper-validator-option" "^7.23.5" - browserslist "^4.22.2" + "@babel/compat-data" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-imports@^7.22.15": - version "7.24.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" - integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== - dependencies: - "@babel/types" "^7.24.0" - -"@babel/helper-module-transforms@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" - integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.20" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" -"@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" - integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== - -"@babel/helper-simple-access@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" - integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== +"@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== dependencies: - "@babel/types" "^7.22.5" + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" +"@babel/helper-plugin-utils@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" + integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== -"@babel/helper-string-parser@^7.23.4": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" - integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== -"@babel/helper-validator-option@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" - integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== -"@babel/helpers@^7.24.4": - version "7.24.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.4.tgz#dc00907fd0d95da74563c142ef4cd21f2cb856b6" - integrity sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw== +"@babel/helpers@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4" + integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw== dependencies: - "@babel/template" "^7.24.0" - "@babel/traverse" "^7.24.1" - "@babel/types" "^7.24.0" - -"@babel/highlight@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" - integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.23.3", "@babel/parser@^7.24.0", "@babel/parser@^7.24.1", "@babel/parser@^7.24.4": +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.4.tgz#234487a110d89ad5a3ed4a8a566c36b9453e8c88" integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg== -"@babel/plugin-syntax-jsx@^7.23.3": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" - integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - -"@babel/plugin-transform-react-jsx@^7.22.5": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz#393f99185110cea87184ea47bcb4a7b0c2e39312" - integrity sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-plugin-utils" "^7.22.5" - "@babel/plugin-syntax-jsx" "^7.23.3" - "@babel/types" "^7.23.4" - -"@babel/template@^7.22.15", "@babel/template@^7.24.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" - integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== - dependencies: - "@babel/code-frame" "^7.23.5" - "@babel/parser" "^7.24.0" - "@babel/types" "^7.24.0" - -"@babel/traverse@^7.23.3", "@babel/traverse@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.1.tgz#d65c36ac9dd17282175d1e4a3c49d5b7988f530c" - integrity sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ== - dependencies: - "@babel/code-frame" "^7.24.1" - "@babel/generator" "^7.24.1" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.24.1" - "@babel/types" "^7.24.0" +"@babel/parser@^7.25.4": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.8.tgz#f6aaf38e80c36129460c1657c0762db584c9d5e2" + integrity sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ== + dependencies: + "@babel/types" "^7.25.8" + +"@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234" + integrity sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA== + dependencies: + "@babel/types" "^7.26.3" + +"@babel/plugin-syntax-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-react-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz#06367940d8325b36edff5e2b9cbe782947ca4166" + integrity sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-jsx" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/template@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/traverse@^7.25.9": + version "7.26.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" + integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.3" + "@babel/parser" "^7.26.3" + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.3" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.3", "@babel/types@^7.23.4", "@babel/types@^7.24.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" - integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.4", "@babel/types@^7.25.8", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" + integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== dependencies: - "@babel/helper-string-parser" "^7.23.4" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" "@ctrl/tinycolor@^3.6.0": version "3.6.1" @@ -385,235 +375,127 @@ resolved "https://registry.yarnpkg.com/@emmetio/scanner/-/scanner-1.0.4.tgz#e9cdc67194fd91f8b7eb141014be4f2d086c15f1" integrity sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA== -"@esbuild/aix-ppc64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz#d1bc06aedb6936b3b6d313bf809a5a40387d2b7f" - integrity sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA== - -"@esbuild/aix-ppc64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" - integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== - -"@esbuild/android-arm64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz#7ad65a36cfdb7e0d429c353e00f680d737c2aed4" - integrity sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA== - -"@esbuild/android-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" - integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== - -"@esbuild/android-arm@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.12.tgz#b0c26536f37776162ca8bde25e42040c203f2824" - integrity sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w== - -"@esbuild/android-arm@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" - integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== - -"@esbuild/android-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.12.tgz#cb13e2211282012194d89bf3bfe7721273473b3d" - integrity sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew== - -"@esbuild/android-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" - integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== - -"@esbuild/darwin-arm64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz#cbee41e988020d4b516e9d9e44dd29200996275e" - integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g== - -"@esbuild/darwin-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" - integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== - -"@esbuild/darwin-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz#e37d9633246d52aecf491ee916ece709f9d5f4cd" - integrity sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A== - -"@esbuild/darwin-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" - integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== - -"@esbuild/freebsd-arm64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz#1ee4d8b682ed363b08af74d1ea2b2b4dbba76487" - integrity sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA== - -"@esbuild/freebsd-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" - integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== - -"@esbuild/freebsd-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz#37a693553d42ff77cd7126764b535fb6cc28a11c" - integrity sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg== - -"@esbuild/freebsd-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" - integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== - -"@esbuild/linux-arm64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz#be9b145985ec6c57470e0e051d887b09dddb2d4b" - integrity sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA== - -"@esbuild/linux-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" - integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== - -"@esbuild/linux-arm@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz#207ecd982a8db95f7b5279207d0ff2331acf5eef" - integrity sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w== - -"@esbuild/linux-arm@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" - integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== - -"@esbuild/linux-ia32@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz#d0d86b5ca1562523dc284a6723293a52d5860601" - integrity sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA== - -"@esbuild/linux-ia32@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" - integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== - -"@esbuild/linux-loong64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz#9a37f87fec4b8408e682b528391fa22afd952299" - integrity sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA== - -"@esbuild/linux-loong64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" - integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== - -"@esbuild/linux-mips64el@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz#4ddebd4e6eeba20b509d8e74c8e30d8ace0b89ec" - integrity sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w== - -"@esbuild/linux-mips64el@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" - integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== - -"@esbuild/linux-ppc64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz#adb67dadb73656849f63cd522f5ecb351dd8dee8" - integrity sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg== - -"@esbuild/linux-ppc64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" - integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== - -"@esbuild/linux-riscv64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz#11bc0698bf0a2abf8727f1c7ace2112612c15adf" - integrity sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg== - -"@esbuild/linux-riscv64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" - integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== - -"@esbuild/linux-s390x@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz#e86fb8ffba7c5c92ba91fc3b27ed5a70196c3cc8" - integrity sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg== - -"@esbuild/linux-s390x@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" - integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== - -"@esbuild/linux-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz#5f37cfdc705aea687dfe5dfbec086a05acfe9c78" - integrity sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg== - -"@esbuild/linux-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" - integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== - -"@esbuild/netbsd-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz#29da566a75324e0d0dd7e47519ba2f7ef168657b" - integrity sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA== - -"@esbuild/netbsd-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" - integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== - -"@esbuild/openbsd-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz#306c0acbdb5a99c95be98bdd1d47c916e7dc3ff0" - integrity sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw== - -"@esbuild/openbsd-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" - integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== - -"@esbuild/sunos-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz#0933eaab9af8b9b2c930236f62aae3fc593faf30" - integrity sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA== - -"@esbuild/sunos-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" - integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== - -"@esbuild/win32-arm64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz#773bdbaa1971b36db2f6560088639ccd1e6773ae" - integrity sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A== - -"@esbuild/win32-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" - integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== - -"@esbuild/win32-ia32@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz#000516cad06354cc84a73f0943a4aa690ef6fd67" - integrity sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ== - -"@esbuild/win32-ia32@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" - integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== - -"@esbuild/win32-x64@0.19.12": - version "0.19.12" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz#c57c8afbb4054a3ab8317591a0b7320360b444ae" - integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA== - -"@esbuild/win32-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" - integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== +"@emnapi/runtime@^1.2.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.3.1.tgz#0fcaa575afc31f455fd33534c19381cfce6c6f60" + integrity sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw== + dependencies: + tslib "^2.4.0" + +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== "@expressive-code/core@^0.33.5": version "0.33.5" @@ -651,6 +533,119 @@ hastscript "^7.2.0" unist-util-visit-parents "^5.1.3" +"@img/sharp-darwin-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08" + integrity sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.0.4" + +"@img/sharp-darwin-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61" + integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.0.4" + +"@img/sharp-libvips-darwin-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz#447c5026700c01a993c7804eb8af5f6e9868c07f" + integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== + +"@img/sharp-libvips-darwin-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062" + integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== + +"@img/sharp-libvips-linux-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704" + integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== + +"@img/sharp-libvips-linux-arm@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197" + integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== + +"@img/sharp-libvips-linux-s390x@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz#f8a5eb1f374a082f72b3f45e2fb25b8118a8a5ce" + integrity sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA== + +"@img/sharp-libvips-linux-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0" + integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== + +"@img/sharp-libvips-linuxmusl-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz#166778da0f48dd2bded1fa3033cee6b588f0d5d5" + integrity sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA== + +"@img/sharp-libvips-linuxmusl-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz#93794e4d7720b077fcad3e02982f2f1c246751ff" + integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== + +"@img/sharp-linux-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22" + integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.0.4" + +"@img/sharp-linux-arm@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff" + integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.0.5" + +"@img/sharp-linux-s390x@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz#f5c077926b48e97e4a04d004dfaf175972059667" + integrity sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.0.4" + +"@img/sharp-linux-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb" + integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.0.4" + +"@img/sharp-linuxmusl-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz#252975b915894fb315af5deea174651e208d3d6b" + integrity sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + +"@img/sharp-linuxmusl-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz#3f4609ac5d8ef8ec7dadee80b560961a60fd4f48" + integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + +"@img/sharp-wasm32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz#6f44f3283069d935bb5ca5813153572f3e6f61a1" + integrity sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg== + dependencies: + "@emnapi/runtime" "^1.2.0" + +"@img/sharp-win32-ia32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz#1a0c839a40c5351e9885628c85f2e5dfd02b52a9" + integrity sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ== + +"@img/sharp-win32-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342" + integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== + "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" @@ -675,6 +670,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" @@ -733,120 +733,171 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@pagefind/darwin-arm64@1.1.0": +"@oslojs/encoding@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@pagefind/darwin-arm64/-/darwin-arm64-1.1.0.tgz#d1b9bcfda0bb099d15b8cc5fcd30e9a1ada8e649" - integrity sha512-SLsXNLtSilGZjvqis8sX42fBWsWAVkcDh1oerxwqbac84HbiwxpxOC2jm8hRwcR0Z55HPZPWO77XeRix/8GwTg== + resolved "https://registry.yarnpkg.com/@oslojs/encoding/-/encoding-1.1.0.tgz#55f3d9a597430a01f2a5ef63c6b42f769f9ce34e" + integrity sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ== -"@pagefind/darwin-x64@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@pagefind/darwin-x64/-/darwin-x64-1.1.0.tgz#182b5d86899b65beb56ae96c828f32c71a5f89bb" - integrity sha512-QjQSE/L5oS1C8N8GdljGaWtjCBMgMtfrPAoiCmINTu9Y9dp0ggAyXvF8K7Qg3VyIMYJ6v8vg2PN7Z3b+AaAqUA== +"@pagefind/darwin-arm64@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@pagefind/darwin-arm64/-/darwin-arm64-1.1.1.tgz#39e75bc62c86cae62a6f903da3b1bacdcbce976c" + integrity sha512-tZ9tysUmQpFs2EqWG2+E1gc+opDAhSyZSsgKmFzhnWfkK02YHZhvL5XJXEZDqYy3s1FAKhwjTg8XDxneuBlDZQ== + +"@pagefind/darwin-x64@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@pagefind/darwin-x64/-/darwin-x64-1.1.1.tgz#a238631d406b8d9ae2bc35e049f5781fc492a6d6" + integrity sha512-ChohLQ39dLwaxQv0jIQB/SavP3TM5K5ENfDTqIdzLkmfs3+JlzSDyQKcJFjTHYcCzQOZVeieeGq8PdqvLJxJxQ== "@pagefind/default-ui@^1.0.3": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@pagefind/default-ui/-/default-ui-1.1.0.tgz#153a4d4621d11adc8d07d679f6b61a9e09594ce9" - integrity sha512-+XiAJAK++C64nQcD7s3Prdmd5S92lT05fwjOxm0L1jj80jbL+tmvcqkkFnPpoqhnicIPgcAX/Y5W0HRZnBt35w== + version "1.1.1" + resolved "https://registry.yarnpkg.com/@pagefind/default-ui/-/default-ui-1.1.1.tgz#ba6c0f1f46f5a548ca3a79e79d3d9c400e783238" + integrity sha512-ZM0zDatWDnac/VGHhQCiM7UgA4ca8jpjA+VfuTJyHJBaxGqZMQnm4WoTz9E0KFcue1Bh9kxpu7uWFZfwpZZk0A== -"@pagefind/linux-arm64@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@pagefind/linux-arm64/-/linux-arm64-1.1.0.tgz#46e8af93106aa202efeae47510e2abcfa3182fa5" - integrity sha512-8zjYCa2BtNEL7KnXtysPtBELCyv5DSQ4yHeK/nsEq6w4ToAMTBl0K06khqxdSGgjMSwwrxvLzq3so0LC5Q14dA== +"@pagefind/linux-arm64@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@pagefind/linux-arm64/-/linux-arm64-1.1.1.tgz#53542dc7873acac5d8fe58f529952bf5598a3bab" + integrity sha512-H5P6wDoCoAbdsWp0Zx0DxnLUrwTGWGLu/VI1rcN2CyFdY2EGSvPQsbGBMrseKRNuIrJDFtxHHHyjZ7UbzaM9EA== -"@pagefind/linux-x64@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@pagefind/linux-x64/-/linux-x64-1.1.0.tgz#6171ce1a6c0c31f8e3f962b9b81d96900ad2019a" - integrity sha512-4lsg6VB7A6PWTwaP8oSmXV4O9H0IHX7AlwTDcfyT+YJo/sPXOVjqycD5cdBgqNLfUk8B9bkWcTDCRmJbHrKeCw== +"@pagefind/linux-x64@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@pagefind/linux-x64/-/linux-x64-1.1.1.tgz#3d37d7132bfa1bc23827ebb918ef3878566c88d6" + integrity sha512-yJs7tTYbL2MI3HT+ngs9E1BfUbY9M4/YzA0yEM5xBo4Xl8Yu8Qg2xZTOQ1/F6gwvMrjCUFo8EoACs6LRDhtMrQ== -"@pagefind/windows-x64@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@pagefind/windows-x64/-/windows-x64-1.1.0.tgz#92efa86baaea76a0268d8d4e692752426cc144b9" - integrity sha512-OboCM76BcMKT9IoSfZuFhiqMRgTde8x4qDDvKulFmycgiJrlL5WnIqBHJLQxZq+o2KyZpoHF97iwsGAm8c32sQ== - -"@rollup/rollup-android-arm-eabi@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz#bddf05c3387d02fac04b6b86b3a779337edfed75" - integrity sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g== - -"@rollup/rollup-android-arm64@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz#b26bd09de58704c0a45e3375b76796f6eda825e4" - integrity sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ== - -"@rollup/rollup-darwin-arm64@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz#c5f3fd1aa285b6d33dda6e3f3ca395f8c37fd5ca" - integrity sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA== - -"@rollup/rollup-darwin-x64@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz#8e4673734d7dc9d68f6d48e81246055cda0e840f" - integrity sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw== - -"@rollup/rollup-linux-arm-gnueabihf@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz#53ed38eb13b58ababdb55a7f66f0538a7f85dcba" - integrity sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw== - -"@rollup/rollup-linux-arm-musleabihf@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz#0706ee38330e267a5c9326956820f009cfb21fcd" - integrity sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw== - -"@rollup/rollup-linux-arm64-gnu@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz#426fce7b8b242ac5abd48a10a5020f5a468c6cb4" - integrity sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA== - -"@rollup/rollup-linux-arm64-musl@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz#65bf944530d759b50d7ffd00dfbdf4125a43406f" - integrity sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw== - -"@rollup/rollup-linux-powerpc64le-gnu@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz#494ba3b31095e9a45df9c3f646d21400fb631a95" - integrity sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw== - -"@rollup/rollup-linux-riscv64-gnu@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz#8b88ed0a40724cce04aa15374ebe5ba4092d679f" - integrity sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ== - -"@rollup/rollup-linux-s390x-gnu@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz#09c9e5ec57a0f6ec3551272c860bb9a04b96d70f" - integrity sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg== - -"@rollup/rollup-linux-x64-gnu@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz#197f27fd481ad9c861021d5cbbf21793922a631c" - integrity sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA== - -"@rollup/rollup-linux-x64-musl@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz#5cc0522f4942f2df625e9bfb6fb02c6580ffbce6" - integrity sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg== - -"@rollup/rollup-win32-arm64-msvc@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz#a648122389d23a7543b261fba082e65fefefe4f6" - integrity sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg== - -"@rollup/rollup-win32-ia32-msvc@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz#34727b5c7953c35fc6e1ae4f770ad3a2025f8e03" - integrity sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw== - -"@rollup/rollup-win32-x64-msvc@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz#5b2fb4d8cd44c05deef8a7b0e6deb9ccb8939d18" - integrity sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA== - -"@shikijs/core@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.3.0.tgz#5b93b51ddb8def1e3a1543107f9b5b0540f716f6" - integrity sha512-7fedsBfuILDTBmrYZNFI8B6ATTxhQAasUHllHmjvSZPnoq4bULWoTpHwmuQvZ8Aq03/tAa2IGo6RXqWtHdWaCA== +"@pagefind/windows-x64@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@pagefind/windows-x64/-/windows-x64-1.1.1.tgz#3d227dd9fe8f11c573a9256a980a5d2f2b073cd7" + integrity sha512-b7/qPqgIl+lMzkQ8fJt51SfguB396xbIIR+VZ3YrL2tLuyifDJ1wL5mEm+ddmHxJ2Fki340paPcDan9en5OmAw== + +"@rollup/pluginutils@^5.1.3": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz#bb94f1f9eaaac944da237767cdfee6c5b2262d4a" + integrity sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^4.0.2" + +"@rollup/rollup-android-arm-eabi@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz#8b613b9725e8f9479d142970b106b6ae878610d5" + integrity sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w== + +"@rollup/rollup-android-arm64@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz#654ca1049189132ff602bfcf8df14c18da1f15fb" + integrity sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA== + +"@rollup/rollup-darwin-arm64@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz#6d241d099d1518ef0c2205d96b3fa52e0fe1954b" + integrity sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q== + +"@rollup/rollup-darwin-x64@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz#42bd19d292a57ee11734c980c4650de26b457791" + integrity sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw== + +"@rollup/rollup-linux-arm-gnueabihf@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz#f23555ee3d8fe941c5c5fd458cd22b65eb1c2232" + integrity sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ== + +"@rollup/rollup-linux-arm-musleabihf@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz#f3bbd1ae2420f5539d40ac1fde2b38da67779baa" + integrity sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg== + +"@rollup/rollup-linux-arm64-gnu@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz#7abe900120113e08a1f90afb84c7c28774054d15" + integrity sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw== + +"@rollup/rollup-linux-arm64-musl@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz#9e655285c8175cd44f57d6a1e8e5dedfbba1d820" + integrity sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA== + +"@rollup/rollup-linux-powerpc64le-gnu@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz#9a79ae6c9e9d8fe83d49e2712ecf4302db5bef5e" + integrity sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg== + +"@rollup/rollup-linux-riscv64-gnu@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz#67ac70eca4ace8e2942fabca95164e8874ab8128" + integrity sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA== + +"@rollup/rollup-linux-s390x-gnu@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz#9f883a7440f51a22ed7f99e1d070bd84ea5005fc" + integrity sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q== + +"@rollup/rollup-linux-x64-gnu@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz#70116ae6c577fe367f58559e2cffb5641a1dd9d0" + integrity sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg== + +"@rollup/rollup-linux-x64-musl@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz#f473f88219feb07b0b98b53a7923be716d1d182f" + integrity sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g== + +"@rollup/rollup-win32-arm64-msvc@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz#4349482d17f5d1c58604d1c8900540d676f420e0" + integrity sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw== + +"@rollup/rollup-win32-ia32-msvc@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz#a6fc39a15db618040ec3c2a24c1e26cb5f4d7422" + integrity sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g== + +"@rollup/rollup-win32-x64-msvc@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz#3dd5d53e900df2a40841882c02e56f866c04d202" + integrity sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q== + +"@shikijs/core@1.24.3": + version "1.24.3" + resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.24.3.tgz#ac8f400a9d66cb68d61a46d0c949adb0dd03fee6" + integrity sha512-VRcf4GYUIkxIchGM9DrapRcxtgojg4IWKUtX5EtW+4PJiGzF2xQqZSv27PJt+WLc18KT3CNLpNWow9JYV5n+Rg== + dependencies: + "@shikijs/engine-javascript" "1.24.3" + "@shikijs/engine-oniguruma" "1.24.3" + "@shikijs/types" "1.24.3" + "@shikijs/vscode-textmate" "^9.3.1" + "@types/hast" "^3.0.4" + hast-util-to-html "^9.0.4" + +"@shikijs/engine-javascript@1.24.3": + version "1.24.3" + resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-1.24.3.tgz#84fd518ef0067a6f4e60a527e3b2fa675a59ec2c" + integrity sha512-De8tNLvYjeK6V0Gb47jIH2M+OKkw+lWnSV1j3HVDFMlNIglmVcTMG2fASc29W0zuFbfEEwKjO8Fe4KYSO6Ce3w== + dependencies: + "@shikijs/types" "1.24.3" + "@shikijs/vscode-textmate" "^9.3.1" + oniguruma-to-es "0.8.0" + +"@shikijs/engine-oniguruma@1.24.3": + version "1.24.3" + resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.24.3.tgz#e549cb6f2050113ac65994b4e98f4704c3e427e8" + integrity sha512-iNnx950gs/5Nk+zrp1LuF+S+L7SKEhn8k9eXgFYPGhVshKppsYwRmW8tpmAMvILIMSDfrgqZ0w+3xWVQB//1Xw== + dependencies: + "@shikijs/types" "1.24.3" + "@shikijs/vscode-textmate" "^9.3.1" + +"@shikijs/types@1.24.3": + version "1.24.3" + resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-1.24.3.tgz#6700007019cc5c2fa5db32ab1595f01b1e79d969" + integrity sha512-FPMrJ69MNxhRtldRk69CghvaGlbbN3pKRuvko0zvbfa2dXp4pAngByToqS5OY5jvN8D7LKR4RJE8UvzlCOuViw== + dependencies: + "@shikijs/vscode-textmate" "^9.3.1" + "@types/hast" "^3.0.4" + +"@shikijs/vscode-textmate@^9.3.1": + version "9.3.1" + resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-9.3.1.tgz#afda31f8f42cab70a26f3603f52eae3f1c35d2f7" + integrity sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g== "@types/acorn@^4.0.0": version "4.0.6" @@ -855,7 +906,7 @@ dependencies: "@types/estree" "*" -"@types/babel__core@^7.20.4": +"@types/babel__core@^7.20.5": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== @@ -888,10 +939,10 @@ dependencies: "@babel/types" "^7.20.7" -"@types/cookie@^0.5.4": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.5.4.tgz#7e70a20cd695bc48d46b08c2505874cd68b760e0" - integrity sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA== +"@types/cookie@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" + integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== "@types/debug@^4.0.0": version "4.1.12" @@ -919,7 +970,7 @@ dependencies: "@types/unist" "^2" -"@types/hast@^3.0.0", "@types/hast@^3.0.3": +"@types/hast@^3.0.0", "@types/hast@^3.0.3", "@types/hast@^3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== @@ -950,6 +1001,13 @@ dependencies: "@types/unist" "^2" +"@types/nlcst@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/nlcst/-/nlcst-2.0.3.tgz#31cad346eaab48a9a8a58465d3d05e2530dda762" + integrity sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA== + dependencies: + "@types/unist" "*" + "@types/node@*": version "20.12.7" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.7.tgz#04080362fa3dd6c5822061aa3124f5c152cff384" @@ -1083,10 +1141,10 @@ acorn-jsx@^5.0.0: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.0.0, acorn@^8.11.2: - version "8.11.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" - integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +acorn@^8.0.0, acorn@^8.11.2, acorn@^8.14.0: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== ansi-align@^3.0.1: version "3.0.1" @@ -1105,13 +1163,6 @@ ansi-regex@^6.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - ansi-styles@^4.0.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -1119,7 +1170,7 @@ ansi-styles@^4.0.0: dependencies: color-convert "^2.0.1" -ansi-styles@^6.1.0: +ansi-styles@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== @@ -1149,12 +1200,10 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" - integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== - dependencies: - dequal "^2.0.3" +aria-query@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" + integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== array-iterate@^2.0.0: version "2.0.1" @@ -1174,83 +1223,80 @@ astro-expressive-code@^0.33.4: hast-util-to-html "^8.0.4" remark-expressive-code "^0.33.5" -astro@^4.3.5: - version "4.6.2" - resolved "https://registry.yarnpkg.com/astro/-/astro-4.6.2.tgz#9d59468abb8b7551bbdf4dec8dc5754301567218" - integrity sha512-Kl+Wd7MJMQFnI3+V0JxF4HPbs8M67eltqQgtztReOwDLSl0VnOd39rM61W/3LEh10FZ0F13xrDgtdgfXzuLVbg== +astro@^4.16.18: + version "4.16.18" + resolved "https://registry.yarnpkg.com/astro/-/astro-4.16.18.tgz#c7db47d5554d865543d6917f42b5129819c6bc88" + integrity sha512-G7zfwJt9BDHEZwlaLNvjbInIw2hPryyD654314KV/XT34pJU6SfN1S+mWa8RAkALcZNJnJXCJmT3JXLQStD3Lw== dependencies: - "@astrojs/compiler" "^2.7.1" - "@astrojs/internal-helpers" "0.4.0" - "@astrojs/markdown-remark" "5.1.0" + "@astrojs/compiler" "^2.10.3" + "@astrojs/internal-helpers" "0.4.1" + "@astrojs/markdown-remark" "5.3.0" "@astrojs/telemetry" "3.1.0" - "@babel/core" "^7.24.3" - "@babel/generator" "^7.23.3" - "@babel/parser" "^7.23.3" - "@babel/plugin-transform-react-jsx" "^7.22.5" - "@babel/traverse" "^7.23.3" - "@babel/types" "^7.23.3" - "@types/babel__core" "^7.20.4" - "@types/cookie" "^0.5.4" - acorn "^8.11.2" - aria-query "^5.3.0" - axobject-query "^4.0.0" - boxen "^7.1.1" - chokidar "^3.5.3" - ci-info "^4.0.0" - clsx "^2.0.0" + "@babel/core" "^7.26.0" + "@babel/plugin-transform-react-jsx" "^7.25.9" + "@babel/types" "^7.26.0" + "@oslojs/encoding" "^1.1.0" + "@rollup/pluginutils" "^5.1.3" + "@types/babel__core" "^7.20.5" + "@types/cookie" "^0.6.0" + acorn "^8.14.0" + aria-query "^5.3.2" + axobject-query "^4.1.0" + boxen "8.0.1" + ci-info "^4.1.0" + clsx "^2.1.1" common-ancestor-path "^1.0.1" - cookie "^0.6.0" + cookie "^0.7.2" cssesc "^3.0.0" - debug "^4.3.4" - deterministic-object-hash "^2.0.1" - devalue "^4.3.2" - diff "^5.1.0" + debug "^4.3.7" + deterministic-object-hash "^2.0.2" + devalue "^5.1.1" + diff "^5.2.0" dlv "^1.1.3" - dset "^3.1.3" - es-module-lexer "^1.4.1" - esbuild "^0.19.6" + dset "^3.1.4" + es-module-lexer "^1.5.4" + esbuild "^0.21.5" estree-walker "^3.0.3" - execa "^8.0.1" fast-glob "^3.3.2" - flattie "^1.1.0" + flattie "^1.1.1" github-slugger "^2.0.0" gray-matter "^4.0.3" html-escaper "^3.0.3" http-cache-semantics "^4.1.1" js-yaml "^4.1.0" - kleur "^4.1.4" - magic-string "^0.30.3" - mime "^3.0.0" - ora "^7.0.1" - p-limit "^5.0.0" + kleur "^4.1.5" + magic-string "^0.30.14" + magicast "^0.3.5" + micromatch "^4.0.8" + mrmime "^2.0.0" + neotraverse "^0.6.18" + ora "^8.1.1" + p-limit "^6.1.0" p-queue "^8.0.1" - path-to-regexp "^6.2.1" - preferred-pm "^3.1.2" + preferred-pm "^4.0.0" prompts "^2.4.2" - rehype "^13.0.1" - resolve "^1.22.4" - semver "^7.5.4" - shiki "^1.1.2" - string-width "^7.0.0" - strip-ansi "^7.1.0" - tsconfck "^3.0.0" + rehype "^13.0.2" + semver "^7.6.3" + shiki "^1.23.1" + tinyexec "^0.3.1" + tsconfck "^3.1.4" unist-util-visit "^5.0.0" - vfile "^6.0.1" - vite "^5.1.4" - vitefu "^0.2.5" - which-pm "^2.1.1" + vfile "^6.0.3" + vite "^5.4.11" + vitefu "^1.0.4" + which-pm "^3.0.0" + xxhash-wasm "^1.1.0" yargs-parser "^21.1.1" - zod "^3.22.4" - zod-to-json-schema "^3.22.4" + zod "^3.23.8" + zod-to-json-schema "^3.23.5" + zod-to-ts "^1.2.0" optionalDependencies: - sharp "^0.32.6" + sharp "^0.33.3" -axobject-query@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.0.0.tgz#04a4c90dce33cc5d606c76d6216e3b250ff70dab" - integrity sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw== - dependencies: - dequal "^2.0.3" +axobject-query@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" + integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== b4a@^1.6.4: version "1.6.6" @@ -1326,50 +1372,41 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" -bl@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-5.1.0.tgz#183715f678c7188ecef9fe475d90209400624273" - integrity sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ== - dependencies: - buffer "^6.0.3" - inherits "^2.0.4" - readable-stream "^3.4.0" - boolbase@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -boxen@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-7.1.1.tgz#f9ba525413c2fec9cdb88987d835c4f7cad9c8f4" - integrity sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog== +boxen@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-8.0.1.tgz#7e9fcbb45e11a2d7e6daa8fdcebfc3242fc19fe3" + integrity sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw== dependencies: ansi-align "^3.0.1" - camelcase "^7.0.1" - chalk "^5.2.0" + camelcase "^8.0.0" + chalk "^5.3.0" cli-boxes "^3.0.0" - string-width "^5.1.2" - type-fest "^2.13.0" - widest-line "^4.0.1" - wrap-ansi "^8.1.0" + string-width "^7.2.0" + type-fest "^4.21.0" + widest-line "^5.0.0" + wrap-ansi "^9.0.0" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: fill-range "^7.1.1" -browserslist@^4.22.2: - version "4.23.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" - integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== +browserslist@^4.24.0: + version "4.24.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" + integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== dependencies: - caniuse-lite "^1.0.30001587" - electron-to-chromium "^1.4.668" - node-releases "^2.0.14" - update-browserslist-db "^1.0.13" + caniuse-lite "^1.0.30001663" + electron-to-chromium "^1.5.28" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" buffer@^5.5.0: version "5.7.1" @@ -1379,39 +1416,22 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - -camelcase@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048" - integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw== +camelcase@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-8.0.0.tgz#c0d36d418753fb6ad9c5e0437579745c1c14a534" + integrity sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA== -caniuse-lite@^1.0.30001587: - version "1.0.30001610" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001610.tgz#2f44ed6e21d359e914271ae35b68903632628ccf" - integrity sha512-QFutAY4NgaelojVMjY63o6XlZyORPaLfyMnsl3HgnWdJUcX6K0oaJymHjH8PT5Gk7sTm8rvC/c5COUQKXqmOMA== +caniuse-lite@^1.0.30001663: + version "1.0.30001668" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz#98e214455329f54bf7a4d70b49c9794f0fbedbed" + integrity sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw== ccount@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^5.0.0, chalk@^5.2.0, chalk@^5.3.0: +chalk@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== @@ -1456,24 +1476,24 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== -ci-info@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.0.0.tgz#65466f8b280fc019b9f50a5388115d17a63a44f2" - integrity sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg== +ci-info@^4.0.0, ci-info@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.1.0.tgz#92319d2fa29d2620180ea5afed31f589bc98cf83" + integrity sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A== cli-boxes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== -cli-cursor@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-4.0.0.tgz#3cecfe3734bf4fe02a8361cbdc0f6fe28c6a57ea" - integrity sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg== +cli-cursor@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-5.0.0.tgz#24a4831ecf5a6b01ddeb32fb71a4b2088b0dce38" + integrity sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== dependencies: - restore-cursor "^4.0.0" + restore-cursor "^5.0.0" -cli-spinners@^2.9.0: +cli-spinners@^2.9.2: version "2.9.2" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== @@ -1487,23 +1507,16 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" -clsx@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" - integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== collapse-white-space@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz#640257174f9f42c740b40f3b55ee752924feefca" integrity sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw== -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -1511,11 +1524,6 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" @@ -1552,19 +1560,10 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -cookie@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" - integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" +cookie@^0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== css-selector-parser@^3.0.0: version "3.0.5" @@ -1576,12 +1575,12 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== +debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.3.4, debug@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: - ms "2.1.2" + ms "^2.1.3" decode-named-character-reference@^1.0.0: version "1.0.2" @@ -1602,27 +1601,27 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -dequal@^2.0.0, dequal@^2.0.3: +dequal@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== -detect-libc@^2.0.0, detect-libc@^2.0.2: +detect-libc@^2.0.0, detect-libc@^2.0.2, detect-libc@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== -deterministic-object-hash@^2.0.1: +deterministic-object-hash@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz#b251ddc801443905f0e9fef08816a46bc9fe3807" integrity sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ== dependencies: base-64 "^1.0.0" -devalue@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/devalue/-/devalue-4.3.2.tgz#cc44e4cf3872ac5a78229fbce3b77e57032727b5" - integrity sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg== +devalue@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/devalue/-/devalue-5.1.1.tgz#a71887ac0f354652851752654e4bd435a53891ae" + integrity sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw== devlop@^1.0.0, devlop@^1.1.0: version "1.1.0" @@ -1631,7 +1630,7 @@ devlop@^1.0.0, devlop@^1.1.0: dependencies: dequal "^2.0.0" -diff@^5.1.0: +diff@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== @@ -1646,20 +1645,15 @@ dlv@^1.1.3: resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== -dset@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.3.tgz#c194147f159841148e8e34ca41f638556d9542d2" - integrity sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ== - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +dset@^3.1.3, dset@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.4.tgz#f8eaf5f023f068a036d08cd07dc9ffb7d0065248" + integrity sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA== -electron-to-chromium@^1.4.668: - version "1.4.738" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.738.tgz#9a7fca98abaee61e20c9c25013d5ce60bb533436" - integrity sha512-lwKft2CLFztD+vEIpesrOtCrko/TFnEJlHFdRhazU7Y/jx5qc4cqsocfVrBg4So4gGe9lvxnbLIoev47WMpg+A== +electron-to-chromium@^1.5.28: + version "1.5.37" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.37.tgz#1660fb87d2bc82a3f8a652ef78eed17cc0d3ef4f" + integrity sha512-u7000ZB/X0K78TaQqXZ5ktoR7J79B9US7IkE4zyvcILYwOGY2Tx9GRPYstn7HmuPcMxZ+BDGqIsyLpZQi9ufPw== emmet@^2.4.3: version "2.4.7" @@ -1669,7 +1663,12 @@ emmet@^2.4.3: "@emmetio/abbreviation" "^2.3.3" "@emmetio/css-abbreviation" "^2.1.8" -emoji-regex@^10.2.1, emoji-regex@^10.3.0: +emoji-regex-xs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz#e8af22e5d9dbd7f7f22d280af3d19d2aab5b0724" + integrity sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg== + +emoji-regex@^10.3.0: version "10.3.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23" integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw== @@ -1679,11 +1678,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -1696,78 +1690,49 @@ entities@^4.4.0: resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== -es-module-lexer@^1.4.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.0.tgz#4878fee3789ad99e065f975fdd3c645529ff0236" - integrity sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw== +es-module-lexer@^1.4.1, es-module-lexer@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== -esbuild@^0.19.6: - version "0.19.12" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.12.tgz#dc82ee5dc79e82f5a5c3b4323a2a641827db3e04" - integrity sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg== - optionalDependencies: - "@esbuild/aix-ppc64" "0.19.12" - "@esbuild/android-arm" "0.19.12" - "@esbuild/android-arm64" "0.19.12" - "@esbuild/android-x64" "0.19.12" - "@esbuild/darwin-arm64" "0.19.12" - "@esbuild/darwin-x64" "0.19.12" - "@esbuild/freebsd-arm64" "0.19.12" - "@esbuild/freebsd-x64" "0.19.12" - "@esbuild/linux-arm" "0.19.12" - "@esbuild/linux-arm64" "0.19.12" - "@esbuild/linux-ia32" "0.19.12" - "@esbuild/linux-loong64" "0.19.12" - "@esbuild/linux-mips64el" "0.19.12" - "@esbuild/linux-ppc64" "0.19.12" - "@esbuild/linux-riscv64" "0.19.12" - "@esbuild/linux-s390x" "0.19.12" - "@esbuild/linux-x64" "0.19.12" - "@esbuild/netbsd-x64" "0.19.12" - "@esbuild/openbsd-x64" "0.19.12" - "@esbuild/sunos-x64" "0.19.12" - "@esbuild/win32-arm64" "0.19.12" - "@esbuild/win32-ia32" "0.19.12" - "@esbuild/win32-x64" "0.19.12" - -esbuild@^0.20.1: - version "0.20.2" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" - integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== +esbuild@^0.21.3, esbuild@^0.21.5: + version "0.21.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== optionalDependencies: - "@esbuild/aix-ppc64" "0.20.2" - "@esbuild/android-arm" "0.20.2" - "@esbuild/android-arm64" "0.20.2" - "@esbuild/android-x64" "0.20.2" - "@esbuild/darwin-arm64" "0.20.2" - "@esbuild/darwin-x64" "0.20.2" - "@esbuild/freebsd-arm64" "0.20.2" - "@esbuild/freebsd-x64" "0.20.2" - "@esbuild/linux-arm" "0.20.2" - "@esbuild/linux-arm64" "0.20.2" - "@esbuild/linux-ia32" "0.20.2" - "@esbuild/linux-loong64" "0.20.2" - "@esbuild/linux-mips64el" "0.20.2" - "@esbuild/linux-ppc64" "0.20.2" - "@esbuild/linux-riscv64" "0.20.2" - "@esbuild/linux-s390x" "0.20.2" - "@esbuild/linux-x64" "0.20.2" - "@esbuild/netbsd-x64" "0.20.2" - "@esbuild/openbsd-x64" "0.20.2" - "@esbuild/sunos-x64" "0.20.2" - "@esbuild/win32-arm64" "0.20.2" - "@esbuild/win32-ia32" "0.20.2" - "@esbuild/win32-x64" "0.20.2" + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" escalade@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== escape-string-regexp@^5.0.0: version "5.0.0" @@ -1818,6 +1783,11 @@ estree-util-visit@^2.0.0: "@types/estree-jsx" "^1.0.0" "@types/unist" "^3.0.0" +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + estree-walker@^3.0.0, estree-walker@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" @@ -1830,21 +1800,6 @@ eventemitter3@^5.0.1: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== -execa@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" - integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^8.0.1" - human-signals "^5.0.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^4.1.0" - strip-final-newline "^3.0.0" - expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" @@ -1902,6 +1857,11 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" +find-up-simple@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/find-up-simple/-/find-up-simple-1.0.0.tgz#21d035fde9fdbd56c8f4d2f63f32fd93a1cfc368" + integrity sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw== + find-up@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -1910,14 +1870,6 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - find-yarn-workspace-root2@1.2.16: version "1.2.16" resolved "https://registry.yarnpkg.com/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz#60287009dd2f324f59646bdb4b7610a6b301c2a9" @@ -1926,7 +1878,7 @@ find-yarn-workspace-root2@1.2.16: micromatch "^4.0.2" pkg-dir "^4.2.0" -flattie@^1.1.0: +flattie@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/flattie/-/flattie-1.1.1.tgz#88182235723113667d36217fec55359275d6fe3d" integrity sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ== @@ -1941,11 +1893,6 @@ fsevents@~2.3.2, fsevents@~2.3.3: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -1961,11 +1908,6 @@ get-east-asian-width@^1.0.0: resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz#5e6ebd9baee6fb8b7b6bd505221065f0cd91f64e" integrity sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA== -get-stream@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" - integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== - github-from-package@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" @@ -2003,18 +1945,6 @@ gray-matter@^4.0.3: section-matter "^1.0.0" strip-bom-string "^1.0.0" -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -hasown@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - hast-util-from-html@^2.0.0, hast-util-from-html@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz#9cd38ee81bf40b2607368b92a04b0905fa987488" @@ -2027,6 +1957,18 @@ hast-util-from-html@^2.0.0, hast-util-from-html@^2.0.1: vfile "^6.0.0" vfile-message "^4.0.0" +hast-util-from-html@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz#485c74785358beb80c4ba6346299311ac4c49c82" + integrity sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw== + dependencies: + "@types/hast" "^3.0.0" + devlop "^1.1.0" + hast-util-from-parse5 "^8.0.0" + parse5 "^7.0.0" + vfile "^6.0.0" + vfile-message "^4.0.0" + hast-util-from-parse5@^7.0.0: version "7.1.2" resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz#aecfef73e3ceafdfa4550716443e4eb7b02e22b0" @@ -2197,6 +2139,23 @@ hast-util-to-html@^9.0.0: stringify-entities "^4.0.0" zwitch "^2.0.4" +hast-util-to-html@^9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.4.tgz#d689c118c875aab1def692c58603e34335a0f5c5" + integrity sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + comma-separated-tokens "^2.0.0" + hast-util-whitespace "^3.0.0" + html-void-elements "^3.0.0" + mdast-util-to-hast "^13.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + stringify-entities "^4.0.0" + zwitch "^2.0.4" + hast-util-to-jsx-runtime@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz#3ed27caf8dc175080117706bf7269404a0aa4f7c" @@ -2250,7 +2209,7 @@ hast-util-to-string@^3.0.0: dependencies: "@types/hast" "^3.0.0" -hast-util-to-text@^4.0.0: +hast-util-to-text@^4.0.0, hast-util-to-text@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz#57b676931e71bf9cb852453678495b3080bfae3e" integrity sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A== @@ -2314,12 +2273,7 @@ http-cache-semantics@^4.1.1: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== -human-signals@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" - integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== - -ieee754@^1.1.13, ieee754@^1.2.1: +ieee754@^1.1.13: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -2329,6 +2283,11 @@ import-meta-resolve@^4.0.0: resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz#0b1195915689f60ab00f830af0f15cc841e8919e" integrity sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA== +import-meta-resolve@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#f9db8bead9fafa61adb811db77a2bf22c5399706" + integrity sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw== + inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" @@ -2379,13 +2338,6 @@ is-buffer@^2.0.0: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== -is-core-module@^2.13.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== - dependencies: - hasown "^2.0.0" - is-decimal@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" @@ -2452,16 +2404,16 @@ is-reference@^3.0.0: dependencies: "@types/estree" "*" -is-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" - integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== - -is-unicode-supported@^1.1.0, is-unicode-supported@^1.3.0: +is-unicode-supported@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714" integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== +is-unicode-supported@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz#09f0ab0de6d3744d48d265ebb98f65d11f2a9b3a" + integrity sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ== + is-wsl@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" @@ -2469,11 +2421,6 @@ is-wsl@^3.0.0: dependencies: is-inside-container "^1.0.0" -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -2494,10 +2441,10 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== json5@^2.2.3: version "2.2.3" @@ -2541,20 +2488,13 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -locate-path@^6.0.0: +log-symbols@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-6.0.0.tgz#bb95e5f05322651cac30c0feb6404f9f2a8a9439" + integrity sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw== dependencies: - p-locate "^5.0.0" - -log-symbols@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-5.1.0.tgz#a20e3b9a5f53fac6aeb8e2bb22c07cf2c8f16d93" - integrity sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA== - dependencies: - chalk "^5.0.0" - is-unicode-supported "^1.1.0" + chalk "^5.3.0" + is-unicode-supported "^1.3.0" longest-streak@^3.0.0: version "3.1.0" @@ -2568,19 +2508,21 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== +magic-string@^0.30.14: + version "0.30.17" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" + integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== dependencies: - yallist "^4.0.0" + "@jridgewell/sourcemap-codec" "^1.5.0" -magic-string@^0.30.3: - version "0.30.10" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e" - integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ== +magicast@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.5.tgz#8301c3c7d66704a0771eb1bad74274f0ec036739" + integrity sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ== dependencies: - "@jridgewell/sourcemap-codec" "^1.4.15" + "@babel/parser" "^7.25.4" + "@babel/types" "^7.25.4" + source-map-js "^1.2.0" markdown-extensions@^2.0.0: version "2.0.0" @@ -2806,11 +2748,6 @@ mdast-util-to-string@^4.0.0: dependencies: "@types/mdast" "^4.0.0" -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -3196,28 +3133,18 @@ micromark@^4.0.0: micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" -micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" -mime@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" - integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-fn@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" - integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +mimic-function@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" + integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== mimic-response@^3.1.0: version "3.1.0" @@ -3234,10 +3161,15 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +mrmime@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.0.tgz#151082a6e06e59a9a39b46b3e14d5cfe92b3abb4" + integrity sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== muggle-string@^0.4.0: version "0.4.1" @@ -3245,15 +3177,20 @@ muggle-string@^0.4.0: integrity sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ== nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== napi-build-utils@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== +neotraverse@^0.6.18: + version "0.6.18" + resolved "https://registry.yarnpkg.com/neotraverse/-/neotraverse-0.6.18.tgz#abcb33dda2e8e713cf6321b29405e822230cdb30" + integrity sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA== + nlcst-to-string@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-3.1.1.tgz#83b90f2e1ee2081e14701317efc26d3bbadc806e" @@ -3261,6 +3198,13 @@ nlcst-to-string@^3.0.0: dependencies: "@types/nlcst" "^1.0.0" +nlcst-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz#05511e8461ebfb415952eb0b7e9a1a7d40471bd4" + integrity sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA== + dependencies: + "@types/nlcst" "^2.0.0" + node-abi@^3.3.0: version "3.58.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.58.0.tgz#3df24fb742e27c3d2a56375ade5dcc68e9aa9710" @@ -3273,10 +3217,10 @@ node-addon-api@^6.1.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== -node-releases@^2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" - integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -3288,13 +3232,6 @@ not@^0.1.0: resolved "https://registry.yarnpkg.com/not/-/not-0.1.0.tgz#c9691c1746c55dcfbe54cbd8bd4ff041bc2b519d" integrity sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA== -npm-run-path@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" - integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== - dependencies: - path-key "^4.0.0" - nth-check@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" @@ -3309,33 +3246,35 @@ once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== +onetime@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-7.0.0.tgz#9f16c92d8c9ef5120e3acd9dd9957cceecc1ab60" + integrity sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== dependencies: - mimic-fn "^2.1.0" + mimic-function "^5.0.0" -onetime@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" - integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== +oniguruma-to-es@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/oniguruma-to-es/-/oniguruma-to-es-0.8.0.tgz#c61efa9c114a39a009fabccd61c583be28db6e53" + integrity sha512-rY+/a6b+uCgoYIL9itjY0x99UUDHXmGaw7Jjk5ZvM/3cxDJifyxFr/Zm4tTmF6Tre18gAakJo7AzhKUeMNLgHA== dependencies: - mimic-fn "^4.0.0" + emoji-regex-xs "^1.0.0" + regex "^5.0.2" + regex-recursion "^5.0.0" -ora@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/ora/-/ora-7.0.1.tgz#cdd530ecd865fe39e451a0e7697865669cb11930" - integrity sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw== +ora@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-8.1.1.tgz#8efc8865e44c87e4b55468a47e80a03e678b0e54" + integrity sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw== dependencies: chalk "^5.3.0" - cli-cursor "^4.0.0" - cli-spinners "^2.9.0" + cli-cursor "^5.0.0" + cli-spinners "^2.9.2" is-interactive "^2.0.0" - is-unicode-supported "^1.3.0" - log-symbols "^5.1.0" - stdin-discarder "^0.1.0" - string-width "^6.1.0" + is-unicode-supported "^2.0.0" + log-symbols "^6.0.0" + stdin-discarder "^0.2.2" + string-width "^7.2.0" strip-ansi "^7.1.0" p-limit@^2.2.0: @@ -3345,19 +3284,12 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-limit@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-5.0.0.tgz#6946d5b7140b649b7a33a027d89b4c625b3a5985" - integrity sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ== +p-limit@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-6.1.0.tgz#d91f9364d3fdff89b0a45c70d04ad4e0df30a0e8" + integrity sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg== dependencies: - yocto-queue "^1.0.0" + yocto-queue "^1.1.1" p-locate@^4.1.0: version "4.1.0" @@ -3366,13 +3298,6 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - p-queue@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-8.0.1.tgz#718b7f83836922ef213ddec263ff4223ce70bef8" @@ -3392,15 +3317,15 @@ p-try@^2.0.0: integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== pagefind@^1.0.3: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pagefind/-/pagefind-1.1.0.tgz#6b758ca9cae28c3776b40db6a3b9478d2286c27b" - integrity sha512-1nmj0/vfYcMxNEQj0YDRp6bTVv9hI7HLdPhK/vBBYlrnwjATndQvHyicj5Y7pUHrpCFZpFnLVQXIF829tpFmaw== + version "1.1.1" + resolved "https://registry.yarnpkg.com/pagefind/-/pagefind-1.1.1.tgz#0fce88df2f2e1ee1c4f92ea024443c78ae271e64" + integrity sha512-U2YR0dQN5B2fbIXrLtt/UXNS0yWSSYfePaad1KcBPTi0p+zRtsVjwmoPaMQgTks5DnHNbmDxyJUL5TGaLljK3A== optionalDependencies: - "@pagefind/darwin-arm64" "1.1.0" - "@pagefind/darwin-x64" "1.1.0" - "@pagefind/linux-arm64" "1.1.0" - "@pagefind/linux-x64" "1.1.0" - "@pagefind/windows-x64" "1.1.0" + "@pagefind/darwin-arm64" "1.1.1" + "@pagefind/darwin-x64" "1.1.1" + "@pagefind/linux-arm64" "1.1.1" + "@pagefind/linux-x64" "1.1.1" + "@pagefind/windows-x64" "1.1.1" parse-entities@^4.0.0: version "4.0.1" @@ -3425,6 +3350,18 @@ parse-latin@^5.0.0: unist-util-modify-children "^3.0.0" unist-util-visit-children "^2.0.0" +parse-latin@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse-latin/-/parse-latin-7.0.0.tgz#8dfacac26fa603f76417f36233fc45602a323e1d" + integrity sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ== + dependencies: + "@types/nlcst" "^2.0.0" + "@types/unist" "^3.0.0" + nlcst-to-string "^4.0.0" + unist-util-modify-children "^4.0.0" + unist-util-visit-children "^3.0.0" + vfile "^6.0.0" + parse5@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" @@ -3447,26 +3384,6 @@ path-exists@^4.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-key@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" - integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@^6.2.1: - version "6.2.2" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36" - integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw== - periscopic@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/periscopic/-/periscopic-3.1.0.tgz#7e9037bf51c5855bd33b48928828db4afa79d97a" @@ -3481,11 +3398,21 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== + pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" @@ -3513,7 +3440,7 @@ postcss-selector-parser@^6.0.11: cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss@^8.4.21, postcss@^8.4.38: +postcss@^8.4.21: version "8.4.38" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== @@ -3522,6 +3449,15 @@ postcss@^8.4.21, postcss@^8.4.38: picocolors "^1.0.0" source-map-js "^1.2.0" +postcss@^8.4.43: + version "8.4.47" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" + integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.0" + source-map-js "^1.2.1" + prebuild-install@^7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056" @@ -3540,15 +3476,14 @@ prebuild-install@^7.1.1: tar-fs "^2.0.0" tunnel-agent "^0.6.0" -preferred-pm@^3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/preferred-pm/-/preferred-pm-3.1.3.tgz#4125ea5154603136c3b6444e5f5c94ecf90e4916" - integrity sha512-MkXsENfftWSRpzCzImcp4FRsCc3y1opwB73CfCNWyzMqArju2CrlMHlqB7VexKiPEOjGMbttv1r9fSCn5S610w== +preferred-pm@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/preferred-pm/-/preferred-pm-4.0.0.tgz#6b256a44d39181fb3829b3abbd9ea2ead6db082b" + integrity sha512-gYBeFTZLu055D8Vv3cSPox/0iTPtkzxpLroSYYA7WXgRi31WCJ51Uyl8ZiPeUUjyvs2MBzK+S8v9JVUgHU/Sqw== dependencies: - find-up "^5.0.0" + find-up-simple "^1.0.0" find-yarn-workspace-root2 "1.2.16" - path-exists "^4.0.0" - which-pm "2.0.0" + which-pm "^3.0.0" prismjs@^1.29.0: version "1.29.0" @@ -3612,6 +3547,25 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +regex-recursion@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/regex-recursion/-/regex-recursion-5.0.0.tgz#330c14e9e448394210dfd063c6a757d7a293e9bb" + integrity sha512-UwyOqeobrCCqTXPcsSqH4gDhOjD5cI/b8kjngWgSZbxYh5yVjAwTjO5+hAuPRNiuR70+5RlWSs+U9PVcVcW9Lw== + dependencies: + regex-utilities "^2.3.0" + +regex-utilities@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/regex-utilities/-/regex-utilities-2.3.0.tgz#87163512a15dce2908cf079c8960d5158ff43280" + integrity sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng== + +regex@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/regex/-/regex-5.0.2.tgz#291d960467e6499a79ceec022d20a4e0df67c54f" + integrity sha512-/pczGbKIQgfTMRV0XjABvc5RzLqQmwqxLHdQao2RTXPk+pmTXB2P0IaUHYdYyk412YLwUIkaeMd5T+RzVgTqnQ== + dependencies: + regex-utilities "^2.3.0" + rehype-parse@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-9.0.0.tgz#3949faeec6f466ec57774215661e0d75469195d9" @@ -3639,10 +3593,19 @@ rehype-stringify@^10.0.0: hast-util-to-html "^9.0.0" unified "^11.0.0" -rehype@^13.0.1: - version "13.0.1" - resolved "https://registry.yarnpkg.com/rehype/-/rehype-13.0.1.tgz#56384ba83955e2f3aa7eca1975b406c67d9dbd5e" - integrity sha512-AcSLS2mItY+0fYu9xKxOu1LhUZeBZZBx8//5HKzF+0XP+eP8+6a5MXn2+DW2kfXR6Dtp1FEXMVrjyKAcvcU8vg== +rehype-stringify@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-10.0.1.tgz#2ec1ebc56c6aba07905d3b4470bdf0f684f30b75" + integrity sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA== + dependencies: + "@types/hast" "^3.0.0" + hast-util-to-html "^9.0.0" + unified "^11.0.0" + +rehype@^13.0.1, rehype@^13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/rehype/-/rehype-13.0.2.tgz#ab0b3ac26573d7b265a0099feffad450e4cf1952" + integrity sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A== dependencies: "@types/hast" "^3.0.0" rehype-parse "^9.0.0" @@ -3709,6 +3672,17 @@ remark-rehype@^11.0.0: unified "^11.0.0" vfile "^6.0.0" +remark-rehype@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.1.tgz#f864dd2947889a11997c0a2667cd6b38f685bca7" + integrity sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + mdast-util-to-hast "^13.0.0" + unified "^11.0.0" + vfile "^6.0.0" + remark-smartypants@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/remark-smartypants/-/remark-smartypants-2.1.0.tgz#afd26d8ff40def346c6516e38b46994449fb2efe" @@ -3718,6 +3692,16 @@ remark-smartypants@^2.0.0: retext-smartypants "^5.2.0" unist-util-visit "^5.0.0" +remark-smartypants@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/remark-smartypants/-/remark-smartypants-3.0.2.tgz#cbaf2b39624c78fcbd6efa224678c1d2e9bc1dfb" + integrity sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA== + dependencies: + retext "^9.0.0" + retext-smartypants "^6.0.0" + unified "^11.0.4" + unist-util-visit "^5.0.0" + remark-stringify@^11.0.0: version "11.0.0" resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-11.0.0.tgz#4c5b01dd711c269df1aaae11743eb7e2e7636fd3" @@ -3737,22 +3721,13 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -resolve@^1.22.4: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -restore-cursor@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-4.0.0.tgz#519560a4318975096def6e609d44100edaa4ccb9" - integrity sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg== +restore-cursor@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-5.1.0.tgz#0766d95699efacb14150993f55baf0953ea1ebe7" + integrity sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" + onetime "^7.0.0" + signal-exit "^4.1.0" retext-latin@^3.0.0: version "3.1.0" @@ -3764,6 +3739,15 @@ retext-latin@^3.0.0: unherit "^3.0.0" unified "^10.0.0" +retext-latin@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/retext-latin/-/retext-latin-4.0.0.tgz#d02498aa1fd39f1bf00e2ff59b1384c05d0c7ce3" + integrity sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA== + dependencies: + "@types/nlcst" "^2.0.0" + parse-latin "^7.0.0" + unified "^11.0.0" + retext-smartypants@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/retext-smartypants/-/retext-smartypants-5.2.0.tgz#da9cb79cc60f36aa33a20a462dfc663bec0068b4" @@ -3774,6 +3758,15 @@ retext-smartypants@^5.2.0: unified "^10.0.0" unist-util-visit "^4.0.0" +retext-smartypants@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/retext-smartypants/-/retext-smartypants-6.2.0.tgz#4e852c2974cf2cfa253eeec427c97efc43b5d158" + integrity sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ== + dependencies: + "@types/nlcst" "^2.0.0" + nlcst-to-string "^4.0.0" + unist-util-visit "^5.0.0" + retext-stringify@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/retext-stringify/-/retext-stringify-3.1.0.tgz#46ed45e077bfc4a8334977f6c2d6611e1d36263a" @@ -3783,6 +3776,15 @@ retext-stringify@^3.0.0: nlcst-to-string "^3.0.0" unified "^10.0.0" +retext-stringify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/retext-stringify/-/retext-stringify-4.0.0.tgz#501d5440bd4d121e351c7c509f8507de9611e159" + integrity sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA== + dependencies: + "@types/nlcst" "^2.0.0" + nlcst-to-string "^4.0.0" + unified "^11.0.0" + retext@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/retext/-/retext-8.1.0.tgz#c43437fb84cd46285ad240a9279142e239bada8d" @@ -3793,34 +3795,44 @@ retext@^8.1.0: retext-stringify "^3.0.0" unified "^10.0.0" +retext@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/retext/-/retext-9.0.0.tgz#ab5cd72836894167b0ca6ae70fdcfaa166267f7a" + integrity sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA== + dependencies: + "@types/nlcst" "^2.0.0" + retext-latin "^4.0.0" + retext-stringify "^4.0.0" + unified "^11.0.0" + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rollup@^4.13.0: - version "4.14.3" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.14.3.tgz#bcbb7784b35826d3164346fa6d5aac95190d8ba9" - integrity sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw== +rollup@^4.20.0: + version "4.22.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.22.4.tgz#4135a6446671cd2a2453e1ad42a45d5973ec3a0f" + integrity sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A== dependencies: "@types/estree" "1.0.5" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.14.3" - "@rollup/rollup-android-arm64" "4.14.3" - "@rollup/rollup-darwin-arm64" "4.14.3" - "@rollup/rollup-darwin-x64" "4.14.3" - "@rollup/rollup-linux-arm-gnueabihf" "4.14.3" - "@rollup/rollup-linux-arm-musleabihf" "4.14.3" - "@rollup/rollup-linux-arm64-gnu" "4.14.3" - "@rollup/rollup-linux-arm64-musl" "4.14.3" - "@rollup/rollup-linux-powerpc64le-gnu" "4.14.3" - "@rollup/rollup-linux-riscv64-gnu" "4.14.3" - "@rollup/rollup-linux-s390x-gnu" "4.14.3" - "@rollup/rollup-linux-x64-gnu" "4.14.3" - "@rollup/rollup-linux-x64-musl" "4.14.3" - "@rollup/rollup-win32-arm64-msvc" "4.14.3" - "@rollup/rollup-win32-ia32-msvc" "4.14.3" - "@rollup/rollup-win32-x64-msvc" "4.14.3" + "@rollup/rollup-android-arm-eabi" "4.22.4" + "@rollup/rollup-android-arm64" "4.22.4" + "@rollup/rollup-darwin-arm64" "4.22.4" + "@rollup/rollup-darwin-x64" "4.22.4" + "@rollup/rollup-linux-arm-gnueabihf" "4.22.4" + "@rollup/rollup-linux-arm-musleabihf" "4.22.4" + "@rollup/rollup-linux-arm64-gnu" "4.22.4" + "@rollup/rollup-linux-arm64-musl" "4.22.4" + "@rollup/rollup-linux-powerpc64le-gnu" "4.22.4" + "@rollup/rollup-linux-riscv64-gnu" "4.22.4" + "@rollup/rollup-linux-s390x-gnu" "4.22.4" + "@rollup/rollup-linux-x64-gnu" "4.22.4" + "@rollup/rollup-linux-x64-musl" "4.22.4" + "@rollup/rollup-win32-arm64-msvc" "4.22.4" + "@rollup/rollup-win32-ia32-msvc" "4.22.4" + "@rollup/rollup-win32-x64-msvc" "4.22.4" fsevents "~2.3.2" run-parallel@^1.1.9: @@ -3853,14 +3865,12 @@ semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.8, semver@^7.5.4: - version "7.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" - integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== - dependencies: - lru-cache "^6.0.0" +semver@^7.3.5, semver@^7.3.8, semver@^7.5.4, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== -sharp@^0.32.5, sharp@^0.32.6: +sharp@^0.32.5: version "0.32.6" resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.6.tgz#6ad30c0b7cd910df65d5f355f774aa4fce45732a" integrity sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w== @@ -3874,29 +3884,46 @@ sharp@^0.32.5, sharp@^0.32.6: tar-fs "^3.0.4" tunnel-agent "^0.6.0" -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shiki@^1.1.2, shiki@^1.1.7: - version "1.3.0" - resolved "https://registry.yarnpkg.com/shiki/-/shiki-1.3.0.tgz#3eda35cb49f6f0a98525e9da48fc072e6c655a3f" - integrity sha512-9aNdQy/etMXctnPzsje1h1XIGm9YfRcSksKOGqZWXA/qP9G18/8fpz5Bjpma8bOgz3tqIpjERAd6/lLjFyzoww== +sharp@^0.33.3: + version "0.33.5" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.5.tgz#13e0e4130cc309d6a9497596715240b2ec0c594e" + integrity sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw== dependencies: - "@shikijs/core" "1.3.0" - -signal-exit@^3.0.2: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + color "^4.2.3" + detect-libc "^2.0.3" + semver "^7.6.3" + optionalDependencies: + "@img/sharp-darwin-arm64" "0.33.5" + "@img/sharp-darwin-x64" "0.33.5" + "@img/sharp-libvips-darwin-arm64" "1.0.4" + "@img/sharp-libvips-darwin-x64" "1.0.4" + "@img/sharp-libvips-linux-arm" "1.0.5" + "@img/sharp-libvips-linux-arm64" "1.0.4" + "@img/sharp-libvips-linux-s390x" "1.0.4" + "@img/sharp-libvips-linux-x64" "1.0.4" + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + "@img/sharp-linux-arm" "0.33.5" + "@img/sharp-linux-arm64" "0.33.5" + "@img/sharp-linux-s390x" "0.33.5" + "@img/sharp-linux-x64" "0.33.5" + "@img/sharp-linuxmusl-arm64" "0.33.5" + "@img/sharp-linuxmusl-x64" "0.33.5" + "@img/sharp-wasm32" "0.33.5" + "@img/sharp-win32-ia32" "0.33.5" + "@img/sharp-win32-x64" "0.33.5" + +shiki@^1.1.2, shiki@^1.1.7, shiki@^1.22.0, shiki@^1.23.1: + version "1.24.3" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-1.24.3.tgz#50eeacd8ce88d054b3ecc6c542283bf7a77a12f6" + integrity sha512-eMeX/ehE2IDKVs71kB4zVcDHjutNcOtm+yIRuR4sA6ThBbdFI0DffGJiyoKCodj0xRGxIoWC3pk/Anmm5mzHmA== + dependencies: + "@shikijs/core" "1.24.3" + "@shikijs/engine-javascript" "1.24.3" + "@shikijs/engine-oniguruma" "1.24.3" + "@shikijs/types" "1.24.3" + "@shikijs/vscode-textmate" "^9.3.1" + "@types/hast" "^3.0.4" signal-exit@^4.1.0: version "4.1.0" @@ -3944,6 +3971,11 @@ source-map-js@^1.2.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + source-map@^0.7.0, source-map@^0.7.4: version "0.7.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" @@ -3959,12 +3991,10 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -stdin-discarder@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.1.0.tgz#22b3e400393a8e28ebf53f9958f3880622efde21" - integrity sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ== - dependencies: - bl "^5.0.0" +stdin-discarder@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz#390037f44c4ae1a1ae535c5fe38dc3aba8d997be" + integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== streamx@^2.13.0, streamx@^2.15.0: version "2.16.1" @@ -3985,24 +4015,6 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -string-width@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-6.1.0.tgz#96488d6ed23f9ad5d82d13522af9e4c4c3fd7518" - integrity sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^10.2.1" - strip-ansi "^7.0.1" - string-width@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.1.0.tgz#d994252935224729ea3719c49f7206dc9c46550a" @@ -4012,6 +4024,15 @@ string-width@^7.0.0: get-east-asian-width "^1.0.0" strip-ansi "^7.1.0" +string-width@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" + integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + dependencies: + emoji-regex "^10.3.0" + get-east-asian-width "^1.0.0" + strip-ansi "^7.1.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -4034,7 +4055,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.1, strip-ansi@^7.1.0: +strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== @@ -4051,11 +4072,6 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== -strip-final-newline@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" - integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== - strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -4075,18 +4091,6 @@ style-to-object@^1.0.0: dependencies: inline-style-parser "0.2.3" -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - tar-fs@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" @@ -4128,10 +4132,10 @@ tar-stream@^3.1.5: fast-fifo "^1.2.0" streamx "^2.15.0" -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== +tinyexec@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.1.tgz#0ab0daf93b43e2c211212396bdb836b468c97c98" + integrity sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ== to-regex-range@^5.0.1: version "5.0.1" @@ -4150,10 +4154,15 @@ trough@^2.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== -tsconfck@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.0.3.tgz#d9bda0e87d05b1c360e996c9050473c7e6f8084f" - integrity sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA== +tsconfck@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.4.tgz#de01a15334962e2feb526824339b51be26712229" + integrity sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ== + +tslib@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== tunnel-agent@^0.6.0: version "0.6.0" @@ -4162,10 +4171,10 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -type-fest@^2.13.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" - integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== +type-fest@^4.21.0: + version "4.26.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.26.1.tgz#a4a17fa314f976dd3e6d6675ef6c775c16d7955e" + integrity sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg== typesafe-path@^0.2.2: version "0.2.2" @@ -4220,6 +4229,19 @@ unified@^11.0.0, unified@^11.0.4: trough "^2.0.0" vfile "^6.0.0" +unified@^11.0.5: + version "11.0.5" + resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== + dependencies: + "@types/unist" "^3.0.0" + bail "^2.0.0" + devlop "^1.0.0" + extend "^3.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^6.0.0" + unist-util-find-after@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz#3fccc1b086b56f34c8b798e1ff90b5c54468e896" @@ -4250,6 +4272,14 @@ unist-util-modify-children@^3.0.0: "@types/unist" "^2.0.0" array-iterate "^2.0.0" +unist-util-modify-children@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz#981d6308e887b005d1f491811d3cbcc254b315e9" + integrity sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw== + dependencies: + "@types/unist" "^3.0.0" + array-iterate "^2.0.0" + unist-util-position-from-estree@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz#d94da4df596529d1faa3de506202f0c9a23f2200" @@ -4300,6 +4330,13 @@ unist-util-visit-children@^2.0.0: dependencies: "@types/unist" "^2.0.0" +unist-util-visit-children@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz#4bced199b71d7f3c397543ea6cc39e7a7f37dc7e" + integrity sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA== + dependencies: + "@types/unist" "^3.0.0" + unist-util-visit-parents@^5.1.1, unist-util-visit-parents@^5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb" @@ -4308,7 +4345,7 @@ unist-util-visit-parents@^5.1.1, unist-util-visit-parents@^5.1.3: "@types/unist" "^2.0.0" unist-util-is "^5.0.0" -unist-util-visit-parents@^6.0.0: +unist-util-visit-parents@^6.0.0, unist-util-visit-parents@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815" integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== @@ -4334,13 +4371,13 @@ unist-util-visit@^5.0.0: unist-util-is "^6.0.0" unist-util-visit-parents "^6.0.0" -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== +update-browserslist-db@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.2.0" + picocolors "^1.1.0" util-deprecate@^1.0.1, util-deprecate@^1.0.2: version "1.0.2" @@ -4389,30 +4426,29 @@ vfile@^5.0.0: unist-util-stringify-position "^3.0.0" vfile-message "^3.0.0" -vfile@^6.0.0, vfile@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.1.tgz#1e8327f41eac91947d4fe9d237a2dd9209762536" - integrity sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw== +vfile@^6.0.0, vfile@^6.0.1, vfile@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== dependencies: "@types/unist" "^3.0.0" - unist-util-stringify-position "^4.0.0" vfile-message "^4.0.0" -vite@^5.1.4: - version "5.2.9" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.9.tgz#cd9a356c6ff5f7456c09c5ce74068ffa8df743d9" - integrity sha512-uOQWfuZBlc6Y3W/DTuQ1Sr+oIXWvqljLvS881SVmAj00d5RdgShLcuXWxseWPd4HXwiYBFW/vXHfKFeqj9uQnw== +vite@^5.4.11: + version "5.4.11" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5" + integrity sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q== dependencies: - esbuild "^0.20.1" - postcss "^8.4.38" - rollup "^4.13.0" + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" optionalDependencies: fsevents "~2.3.3" -vitefu@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-0.2.5.tgz#c1b93c377fbdd3e5ddd69840ea3aa70b40d90969" - integrity sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q== +vitefu@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-1.0.4.tgz#8e0355362d2f64c499cbb22d5dbc3184d02c9a2d" + integrity sha512-y6zEE3PQf6uu/Mt6DTJ9ih+kyJLr4XcSgHR2zUkM8SWDhuixEJxfJ6CZGMHh1Ec3vPLoEA0IHU5oWzVqw8ulow== volar-service-css@0.0.34: version "0.0.34" @@ -4538,35 +4574,19 @@ which-pm-runs@^1.1.0: resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.1.0.tgz#35ccf7b1a0fce87bd8b92a478c9d045785d3bf35" integrity sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA== -which-pm@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-pm/-/which-pm-2.0.0.tgz#8245609ecfe64bf751d0eef2f376d83bf1ddb7ae" - integrity sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w== - dependencies: - load-yaml-file "^0.2.0" - path-exists "^4.0.0" - -which-pm@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/which-pm/-/which-pm-2.1.1.tgz#0be2b70c67e94a32e87b9768a94a7f0954f2dcfa" - integrity sha512-xzzxNw2wMaoCWXiGE8IJ9wuPMU+EYhFksjHxrRT8kMT5SnocBPRg69YAMtyV4D12fP582RA+k3P8H9J5EMdIxQ== +which-pm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/which-pm/-/which-pm-3.0.0.tgz#78f2088b345a63cec9f838b390332fb1e680221f" + integrity sha512-ysVYmw6+ZBhx3+ZkcPwRuJi38ZOTLJJ33PSHaitLxSKUMsh0LkKd0nC69zZCwt5D+AYUcMK2hhw4yWny20vSGg== dependencies: load-yaml-file "^0.2.0" - path-exists "^4.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" -widest-line@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" - integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== +widest-line@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-5.0.0.tgz#b74826a1e480783345f0cd9061b49753c9da70d0" + integrity sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA== dependencies: - string-width "^5.0.1" + string-width "^7.0.0" wrap-ansi@^7.0.0: version "7.0.0" @@ -4577,20 +4597,25 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== +wrap-ansi@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e" + integrity sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q== dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" + ansi-styles "^6.2.1" + string-width "^7.0.0" + strip-ansi "^7.1.0" wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +xxhash-wasm@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz#ffe7f0b98220a4afac171e3fb9b6d1f8771f015e" + integrity sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" @@ -4601,11 +4626,6 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" @@ -4624,25 +4644,25 @@ yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yocto-queue@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110" + integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g== -yocto-queue@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" - integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== - -zod-to-json-schema@^3.22.4: - version "3.22.5" - resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.22.5.tgz#3646e81cfc318dbad2a22519e5ce661615418673" - integrity sha512-+akaPo6a0zpVCCseDed504KBJUQpEW5QZw7RMneNmKw+fGaML1Z9tUNLnHHAC8x6dzVRO1eB2oEMyZRnuBZg7Q== - -zod@^3.22.4: - version "3.22.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" - integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== +zod-to-json-schema@^3.23.5: + version "3.24.1" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz#f08c6725091aadabffa820ba8d50c7ab527f227a" + integrity sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w== + +zod-to-ts@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/zod-to-ts/-/zod-to-ts-1.2.0.tgz#873a2fd8242d7b649237be97e0c64d7954ae0c51" + integrity sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA== + +zod@^3.22.4, zod@^3.23.8: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== zwitch@^2.0.0, zwitch@^2.0.4: version "2.0.4" diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 155177b95c..9854976d45 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -80,6 +80,11 @@ spec: secretKeyRef: name: scrumlr-secrets key: WEBHOOK_URL + - name: SESSION_SECRET + valueFrom: + secretKeyRef: + name: scrumlr-secrets + key: SESSION_SECRET - name: SCRUMLR_BASE_PATH value: "/api" ports: @@ -222,7 +227,7 @@ kind: Cluster metadata: name: cluster-PR_NUMBER spec: - imageName: ghcr.io/cloudnative-pg/postgresql:16.3 + imageName: ghcr.io/cloudnative-pg/postgresql:16.4 instances: 1 storage: storageClass: csi-cinder-sc-delete diff --git a/nginx.conf b/nginx.conf index 73dcd7248c..64820e5ea4 100644 --- a/nginx.conf +++ b/nginx.conf @@ -37,6 +37,7 @@ server { add_header Set-Cookie "scrumlr__websocket-url=${SCRUMLR_WEBSOCKET_URL};Path=/;Max-Age=3600"; add_header Set-Cookie "scrumlr__analytics_data_domain=${SCRUMLR_ANALYTICS_DATA_DOMAIN};Path=/;Max-Age=3600"; add_header Set-Cookie "scrumlr__analytics_src=${SCRUMLR_ANALYTICS_SRC};Path=/;Max-Age=3600"; + add_header Set-Cookie "scrumlr__clarity_id=${SCRUMLR_CLARITY_ID};Path=/;Max-Age=3600"; # Disable caching for index.html add_header Cache-Control "no-store, no-cache, must-revalidate"; diff --git a/package.json b/package.json index 5ad0f09813..09c94c2f35 100644 --- a/package.json +++ b/package.json @@ -6,100 +6,102 @@ "url": "https://github.com/inovex/scrumlr.io/issues", "email": "info@scrumlr.io" }, - "version": "3.7.1", + "version": "3.10.1", "private": true, "license": "MIT", "dependencies": { - "@dnd-kit/core": "^6.1.0", - "@dnd-kit/modifiers": "^7.0.0", - "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@fontsource/raleway": "^5.0.19", - "@react-hook/resize-observer": "^2.0.1", - "@react-spring/web": "^9.7.4", - "@reduxjs/toolkit": "^1.9.7", + "@fontsource/raleway": "^5.1.1", + "@microsoft/clarity": "^1.0.0", + "@react-hook/resize-observer": "^2.0.2", + "@react-spring/web": "^9.7.5", + "@reduxjs/toolkit": "^2.5.0", "avataaars": "^2.0.0", "classnames": "^2.5.1", "file-saver": "^2.0.5", - "i18next": "^23.12.2", - "i18next-browser-languagedetector": "^8.0.0", + "i18next": "^24.2.1", + "i18next-browser-languagedetector": "^8.0.2", "js-cookie": "^3.0.5", - "linkify-react": "^4.1.3", - "linkifyjs": "^4.1.3", - "marked": "13.0.2", + "js-md5": "^0.8.3", + "linkify-react": "^4.2.0", + "linkifyjs": "^4.2.0", + "marked": "15.0.6", "plausible-tracker": "^0.3.9", - "qrcode.react": "^3.1.0", - "query-string": "^9.1.0", + "qrcode.react": "^4.2.0", + "query-string": "^9.1.1", "react": "^18.3.1", "react-autosize-textarea": "^7.1.0", "react-dom": "^18.3.1", - "react-focus-lock": "^2.12.1", + "react-focus-lock": "^2.13.5", "react-helmet": "^6.1.0", - "react-hotkeys-hook": "^4.5.0", - "react-i18next": "^15.0.0", - "react-redux": "^9.1.2", - "react-router": "^6.22.0", - "react-router-dom": "^6.23.1", - "react-to-print": "^2.15.1", - "react-toastify": "^10.0.5", - "react-tooltip": "^5.27.1", + "react-hotkeys-hook": "^4.6.1", + "react-i18next": "^15.4.0", + "react-redux": "^9.2.0", + "react-router": "^7.1.1", + "react-snowfall": "^2.2.0", + "react-to-print": "^3.0.4", + "react-toastify": "^11.0.2", + "react-tooltip": "^5.28.0", "sockette": "^2.0.6", - "underscore": "^1.13.6", + "underscore": "^1.13.7", "use-long-press": "^3.2.0", "use-sound": "^4.0.3" }, "devDependencies": { "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^6.4.8", - "@testing-library/react": "^16.0.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.1.0", "@testing-library/user-event": "^14.5.2", - "@types/classnames": "^2.3.1", + "@types/classnames": "^2.3.4", "@types/file-saver": "^2.0.7", "@types/history": "^5.0.0", "@types/i18next-fs-backend": "^1.1.5", - "@types/jest": "^29.5.12", + "@types/jest": "^29.5.14", "@types/js-cookie": "^3.0.6", - "@types/node": "^20.14.12", - "@types/qrcode.react": "^1.0.5", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", + "@types/node": "^22.10.5", + "@types/qrcode.react": "^3.0.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", "@types/react-helmet": "^6.1.11", "@types/redux-mock-store": "^1.0.6", "@types/smoothscroll-polyfill": "^0.3.4", - "@types/underscore": "^1.11.15", + "@types/underscore": "^1.13.0", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "cross-env": "^7.0.3", - "cypress": "^13.13.1", - "eslint": "^8.57.0", + "cypress": "^13.17.0", + "eslint": "^8.57.1", "eslint-config-airbnb": "19.0.4", "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-prettier": "^9.1.0", - "eslint-import-resolver-typescript": "^3.6.1", + "eslint-import-resolver-typescript": "^3.7.0", "eslint-plugin-css-modules": "^2.12.0", - "eslint-plugin-import": "2.29.1", - "eslint-plugin-jest": "^28.6.0", - "eslint-plugin-jsx-a11y": "6.9.0", + "eslint-plugin-import": "2.31.0", + "eslint-plugin-jest": "^28.10.0", + "eslint-plugin-jsx-a11y": "6.10.2", "eslint-plugin-only-warn": "^1.1.0", "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-react": "7.35.0", + "eslint-plugin-react": "7.37.4", "eslint-plugin-react-hooks": "4.6.2", "hash-files": "^1.1.1", - "husky": "^9.1.1", - "i18next-fs-backend": "^2.3.1", - "lint-staged": "^15.2.7", - "prettier": "^3.3.3", + "husky": "^9.1.7", + "i18next-fs-backend": "^2.6.0", + "lint-staged": "^15.3.0", + "prettier": "^3.4.2", "react-dnd-test-backend": "^15.1.2", "react-dnd-test-utils": "^15.1.2", "react-scripts": "^5.0.1", "redux-mock-store": "^1.5.4", "resize-observer-polyfill": "^1.5.1", - "sass": "^1.77.8", - "stylelint": "^16.7.0", + "sass": "^1.83.1", + "stylelint": "^16.13.0", "stylelint-config-standard": "^36.0.1", - "ts-jest": "^29.2.3", - "typescript": "^5.4.5" + "ts-jest": "^29.2.5", + "typescript": "^5.7.3" }, "scripts": { "start": "react-scripts start", diff --git a/server/api.postman_collection.json b/server/api.postman_collection.json index ff6b9f5b90..162ad165fd 100644 --- a/server/api.postman_collection.json +++ b/server/api.postman_collection.json @@ -1,8 +1,8 @@ { "info": { - "_postman_id": "42d60e2e-95bb-4336-8893-afed92d835c3", + "_postman_id": "80a95c37-7852-4687-af86-b1d585417c2e", "name": "scrumlr.io", - "description": "This is the documentation for the REST API server of the application [scrumlr.io](https://scrumlr.io). You get in touch with us and send an email to [info@scrumlr.io](https://info@scrumlr.io). The software is [MIT licensed](https://opensource.org/licenses/MIT) so do whatever you want with it. If you want to checkout the progress of our development and take a peek into our backlog you can checkout our [GitHub repository](https://github.com/inovex/scrumlr.io). By the way, this already the third iteration of our server and we're still working on the interface and on further improvements. Since the API is mainly intended for our web client we won't start with API versions at the moment so breaking changes may be incoming. Once it got stable we'll maybe start with that.\n\nIf you're using the postman collection in order to explore the different resources you should also checkout the variables of the collection. Anytime you'll create new resources (e.g. your login or a board) variables will be stored and used for subsequent calls on other resources.\n\nAccess to protected resources will be authorized if a bearer token is sent or it is included in the `jwt` Cookie, which will be automatically set upon login.\n\n## Getting started\n\nLet's try to explain the basic flow of how a new board can will be created and someone tries to join the board as a participant.\n\nFirst you can check whether you are already logged in by a `GET` request on `/user`. See the _User_ section for more information.\n\n1. A user signs into the application (see _Login_ section)\n2. The user creates a new board (`POST` on `/boards`, checkout _Boards_ section)\n3. Another logged in user tries to join the board (`POST` on `/boards/{id}/participants`, checkout _Participants_ section)\n 1. If the boards access policy is set to `PUBLIC` the participant will be added to the board and afterwards all resources will be available\n 2. If the board requires a passphrase and the access policy is set to `BY_PASSPHRASE` a client error will be reported until the user sends the correct passphrase within the payload of the request\n 3. If the boards access policy is set to `BY_INVITE` a session request will be created instead and the user will be redirected to the new resource. The board owner now needs to accept or reject the request until the user can continue\n\nThese are just the basic steps of how sessions can be created. You can also have a look into the section _Realtime_ to see how you can open websockets and listen to live updates on the data.", + "description": "This is the documentation for the REST API server of the application [scrumlr.io](https://scrumlr.io). You get in touch with us and send an email to [info@scrumlr.io](https://info@scrumlr.io). The software is [MIT licensed](https://opensource.org/licenses/MIT) so do whatever you want with it. If you want to checkout the progress of our development and take a peek into our backlog you can checkout our [GitHub repository](https://github.com/inovex/scrumlr.io). By the way, this already the third iteration of our server and we're still working on the interface and on further improvements. Since the API is mainly intended for our web client we won't start with API versions at the moment so breaking changes may be incoming. Once it got stable we'll maybe start with that.\n\nIf you're using the postman collection in order to explore the different resources you should also checkout the variables of the collection. Anytime you'll create new resources (e.g. your login or a board) variables will be stored and used for subsequent calls on other resources.\n\nAccess to protected resources will be authorized if a bearer token is sent or it is included in the `jwt` Cookie, which will be automatically set upon login.\n\n## Getting started\n\nLet's try to explain the basic flow of how a new board can will be created and someone tries to join the board as a participant.\n\nFirst you can check whether you are already logged in by a `GET` request on `/user`. See the _User_ section for more information.\n\n1. A user signs into the application (see _Login_ section)\n \n2. The user creates a new board (`POST` on `/boards`, checkout _Boards_ section)\n \n3. Another logged in user tries to join the board (`POST` on `/boards/{id}/participants`, checkout _Participants_ section)\n 1. If the boards access policy is set to `PUBLIC` the participant will be added to the board and afterwards all resources will be available\n \n 2. If the board requires a passphrase and the access policy is set to `BY_PASSPHRASE` a client error will be reported until the user sends the correct passphrase within the payload of the request\n \n 3. If the boards access policy is set to `BY_INVITE` a session request will be created instead and the user will be redirected to the new resource. The board owner now needs to accept or reject the request until the user can continue\n \n\nThese are just the basic steps of how sessions can be created. You can also have a look into the section _Realtime_ to see how you can open websockets and listen to live updates on the data.", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_exporter_id": "32423964" }, @@ -245,6 +245,35 @@ } }, "response": [] + }, + { + "name": "Sign-in with OIDC", + "protocolProfileBehavior": { + "followRedirects": false + }, + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/login/oidc", + "host": [ + "{{base_url}}" + ], + "path": [ + "login", + "oidc" + ], + "query": [ + { + "key": "state", + "value": "", + "description": "URL to redirect to on successful authentication", + "disabled": true + } + ] + } + }, + "response": [] } ], "description": "These resources can be used to sign-in into the application.\n\nThe login's with third party providers will automatically sign-in the user, sync the user profile (e.g. the name or avatar URL) and redirect back to the URL specified by the `state` query parameter. Do not worry about CSRF vulnerabilities here, since we'll enrich your URL with a randomly generated nonce.", @@ -514,12 +543,8 @@ " pm.expect(res.name).to.exist;", "})", "", - "const jsonData = pm.response.json();", "pm.collectionVariables.set(\"board_template_id\", res.id);", - "", - "// set two column ids", - "pm.collectionVariables.set(\"board_template_column_id_one\", res.templateColumns[0].id)", - "pm.collectionVariables.set(\"board_template_column_id_two\", res.templateColumns[1].id)" + "" ], "type": "text/javascript", "packages": {} @@ -531,7 +556,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"name\": \"My board template\",\n \"description\": \"This is a test description for a board template\",\n \"accessPolicy\": \"PUBLIC\",\n \"favourite\": true,\n \"columns\": [ \n { \n \"name\": \"Lean coffee\", \n \"description\":\"A template column description\", \n \"visible\": true,\n \"color\": \"backlog-blue\"\n },\n { \n \"name\": \"Actions\", \n \"description\":\"A template column description\", \n \"visible\": false, \n \"color\": \"planning-pink\"\n }\n ]\n}", + "raw": "{\n \"name\": \"My board template\",\n \"description\": \"This is a test description for a board template\",\n \"accessPolicy\": \"PUBLIC\",\n \"favourite\": true,\n \"columnTemplates\": [ \n { \n \"name\": \"Lean coffee\", \n \"description\":\"A template column description\", \n \"visible\": true,\n \"color\": \"backlog-blue\"\n },\n { \n \"name\": \"Actions\", \n \"description\":\"A template column description\", \n \"visible\": false, \n \"color\": \"planning-pink\"\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -885,7 +910,7 @@ "response": [] }, { - "name": "Update Favourite status", + "name": "Update Board Template favourite status", "event": [ { "listen": "test", @@ -938,75 +963,6 @@ } }, "response": [] - }, - { - "name": "Update Board Template columns", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "const res = pm.response.json();", - "", - "pm.test(\"Successful POST request\", () => {", - " pm.expect(pm.response).to.have.status(200);", - "});", - "", - "pm.test(\"Check id is included\", () => {", - " pm.expect(res.id).to.exist;", - "})", - "", - "pm.test(\"Check name is included\", () => {", - " pm.expect(res.name).to.exist;", - "})", - "", - "pm.test(\"Check if column #1 changed\", () => {", - " const columnOne = res.templateColumns[0];", - " pm.expect(columnOne.description).to.equal(\"Updated Column #1 Template Description\");", - " pm.expect(columnOne.name).to.equal(\"Updated Lean coffee\");", - " pm.expect(columnOne.color).to.equal(\"planning-pink\");", - " pm.expect(columnOne.index).to.equal(1);", - " pm.expect(columnOne.visible).to.equal(false);", - "})", - "", - "pm.test(\"Check if column #2 changed\", () => {", - " const columnTwo = pm.response.json().templateColumns[1];", - " pm.expect(columnTwo.description).to.equal(\"Updated Column #2 Template Description\");", - " pm.expect(columnTwo.name).to.equal(\"Updated Actions\");", - " pm.expect(columnTwo.color).to.equal(\"backlog-blue\");", - " pm.expect(columnTwo.index).to.equal(0);", - " pm.expect(columnTwo.visible).to.equal(true);", - "})" - ], - "type": "text/javascript", - "packages": {} - } - } - ], - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"Updated: Columns\",\n \"templateColumns\": [\n {\n \"id\": \"{{board_template_column_id_one}}\",\n \"name\": \"Updated Lean coffee\",\n \"description\": \"Updated Column #1 Template Description\",\n \"color\": \"planning-pink\",\n \"visible\": false,\n \"index\": 1\n },\n {\n \"id\": \"{{board_template_column_id_two}}\",\n \"name\": \"Updated Actions\",\n \"description\": \"Updated Column #2 Template Description\",\n \"color\": \"backlog-blue\",\n \"visible\": true,\n \"index\": 0\n }\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{base_url}}/templates/{{board_template_id}}", - "host": [ - "{{base_url}}" - ], - "path": [ - "templates", - "{{board_template_id}}" - ] - } - }, - "response": [] } ] }, @@ -1061,12 +1017,25 @@ "exec": [ "const res = pm.response.json();", "", - "pm.test(\"Successful POST request\", () => {", + "pm.test(\"Successful GET request\", () => {", " pm.expect(pm.response).to.have.status(200);", "});", "", "pm.test(\"Check that an array of board templates are returned\", () => {", " pm.expect(res).to.be.an('array');", + "})", + "", + "", + "pm.test(\"Check that first entry of array includes column templates\", () => {", + " pm.expect(res[0].ColumnTemplates).to.exist;", + "})", + "", + "pm.test(\"Check that column template is not null\", () => {", + " pm.expect(res[0].ColumnTemplates).to.not.be.null;", + "})", + "", + "pm.test(\"Check that column template is an array\", () => {", + " pm.expect(res[0].ColumnTemplates).to.be.an('array');", "})" ], "type": "text/javascript", @@ -1124,6 +1093,295 @@ } ] }, + { + "name": "ColumnTemplates", + "item": [ + { + "name": "Create column template", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const res = pm.response.json();", + "", + "pm.test(\"Successful POST request\", () => {", + " pm.expect(pm.response).to.have.status(201);", + "});", + "", + "pm.test(\"Check id is included\", () => {", + " pm.expect(res.id).to.exist;", + "})", + "", + "pm.test(\"Check name is included\", () => {", + " pm.expect(res.name).to.exist;", + "})", + "", + "pm.collectionVariables.set(\"column_template_id\", res.id);" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "let body = ", + " {", + " \"name\": \"My new board template\",", + " \"description\": \"This is a new test description for a board template\",", + " \"accessPolicy\": \"PUBLIC\",", + " \"favourite\": true,", + " \"columnTemplates\": [ ", + " { ", + " \"name\": \"Lean coffee\", ", + " \"description\":\"A template column description\", ", + " \"visible\": true,", + " \"color\": \"backlog-blue\"", + " },", + " { ", + " \"name\": \"Actions\", ", + " \"description\":\"A template column description\", ", + " \"visible\": false, ", + " \"color\": \"planning-pink\"", + " }", + " ]", + " };", + "", + "body = JSON.stringify(body)", + "", + "pm.sendRequest({", + " url: pm.variables.get(\"base_url\") + \"/templates\",", + " method: 'POST',", + " header: {", + " \"Content-Type\": \"application/json; charset=utf-8\",", + " \"Authorization\": pm.collectionVariables.get(\"jwt\"),", + " },", + " body: body", + " ", + "}, function (err, response) {", + " const res = response.json();", + "", + " pm.test(\"PreRequestScript – Successful POST request (createBoardTemplate)\", () => {", + " pm.expect(response.code).to.be.equal(201);", + " });", + " ", + " pm.test(\"PreRequestScript – Check id\", () => {", + " pm.expect(res.id).to.exist;", + " })", + "", + " pm.collectionVariables.set(\"board_template_id\", res.id);", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"A created column\",\n \"description\": \"A created column description\",\n \"color\": \"backlog-blue\",\n \"visible\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/templates/{{board_template_id}}/columns", + "host": [ + "{{base_url}}" + ], + "path": [ + "templates", + "{{board_template_id}}", + "columns" + ] + } + }, + "response": [] + }, + { + "name": "Get column templates", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const res = pm.response.json();", + "", + "pm.test(\"Successful GET request\", () => {", + " pm.expect(pm.response).to.have.status(200);", + "});", + "", + "pm.test(\"Check that an array of board templates are returned\", () => {", + " pm.expect(res).to.be.an('array');", + "})" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/templates/{{board_template_id}}/columns", + "host": [ + "{{base_url}}" + ], + "path": [ + "templates", + "{{board_template_id}}", + "columns" + ] + } + }, + "response": [] + }, + { + "name": "Get column template", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const res = pm.response.json();", + "", + "pm.test(\"Successful GET request\", () => {", + " pm.expect(pm.response).to.have.status(200);", + "});", + "", + "pm.test(\"Check id is included\", () => {", + " pm.expect(res.id).to.exist;", + "})", + "", + "pm.test(\"Check name is included\", () => {", + " pm.expect(res.name).to.exist;", + "})" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/templates/{{board_template_id}}/columns/{{column_template_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "templates", + "{{board_template_id}}", + "columns", + "{{column_template_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Update column template", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const res = pm.response.json();", + "", + "pm.test(\"Successful PUT request\", () => {", + " pm.expect(pm.response).to.have.status(200);", + "});", + "", + "pm.test(\"Check id is included\", () => {", + " pm.expect(res.id).to.exist;", + "})", + "", + "pm.test(\"Check if column changed\", () => {", + " pm.expect(res.name).to.equal(\"Updated Column Template Name\");", + " pm.expect(res.description).to.equal(\"Updated Column Template Description\");", + " pm.expect(res.color).to.equal(\"goal-green\");", + " pm.expect(res.visible).to.equal(false);", + " pm.expect(res.index).to.equal(0);", + "})" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Updated Column Template Name\",\n \"description\": \"Updated Column Template Description\",\n \"color\": \"goal-green\",\n \"visible\": false,\n \"index\": 0\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/templates/{{board_template_id}}/columns/{{column_template_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "templates", + "{{board_template_id}}", + "columns", + "{{column_template_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Delete column template", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Successful DELETE request\", () => {", + " pm.expect(pm.response).to.have.status(204);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{base_url}}/templates/{{board_template_id}}/columns/{{column_template_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "templates", + "{{board_template_id}}", + "columns", + "{{column_template_id}}" + ] + } + }, + "response": [] + } + ] + }, { "name": "Boards", "item": [ @@ -5518,14 +5776,8 @@ "type": "string" }, { - "key": "board_template_column_id_one", - "value": "{board_template_column_id_one}", - "type": "string" - }, - { - "key": "board_template_column_id_two", - "value": "{board_template_column_id_two}", - "type": "string" + "key": "column_template_id", + "value": "{column_template_id}" } ] -} \ No newline at end of file +} diff --git a/server/config_example.toml b/server/config_example.toml index 7dc66242e0..a820cef96c 100644 --- a/server/config_example.toml +++ b/server/config_example.toml @@ -31,6 +31,9 @@ base-path = "/" # Enable/Disable the login of anonymous clients disable-anonymous-login = false +# enables/disables experimental file system store, in order to allow larger session cookie sizes +auth-experimental-file-system-store = false + # Set the protocol and host for the auth provider callbacks (e.g. https://scrumlr.io) auth-callback-host = "" @@ -67,6 +70,21 @@ auth-apple-client-id = "" # Set the client secret for Apple OAuth authentication. auth-apple-client-secret = "" +# Set the client ID for OIDC authentication. +auth-oidc-client-id = "" + +# Set the client secret for OIDC authentication. +auth-oidc-client-secret = "" + +# Set the URL hosting the OIDC discovery document. +auth-oidc-discovery-url = "" + +# Set the JWT scope to request from the IDP for user identifier information. +auth-oidc-user-ident-scope = "" + +# Set the JWT scope to request from the IDP for user name information. +auth-oidc-user-name-scope = "" + # Enable or disable verbose logging. verbose = true diff --git a/server/docker-compose.dev.yml b/server/docker-compose.dev.yml index 1d0625d847..a6c8c0209b 100644 --- a/server/docker-compose.dev.yml +++ b/server/docker-compose.dev.yml @@ -8,7 +8,7 @@ services: - "4222:4222" database: - image: postgres:16.3 + image: postgres:16.4 ports: - "5432:5432" environment: diff --git a/server/docker-compose.yml b/server/docker-compose.yml index cb9b505cb1..938b62b0e0 100644 --- a/server/docker-compose.yml +++ b/server/docker-compose.yml @@ -17,7 +17,7 @@ services: - build database: - image: postgres:16.3 + image: postgres:16.4 ports: - "5432:5432" environment: diff --git a/server/src/Dockerfile b/server/src/Dockerfile index 981731f8fa..387c5fbe5b 100644 --- a/server/src/Dockerfile +++ b/server/src/Dockerfile @@ -1,8 +1,12 @@ # Build application -FROM golang:1.22 +FROM golang:1.23 + +ARG TARGETARCH +ARG TARGETOS + WORKDIR /go/src/github.com/inovex/scrumlr.io/ COPY ./ . -RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -o main +RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -a -o main # Start application FROM alpine:3.20 diff --git a/server/src/api/board_reaction.go b/server/src/api/board_reaction.go index 2de5766442..69770ec259 100644 --- a/server/src/api/board_reaction.go +++ b/server/src/api/board_reaction.go @@ -7,16 +7,19 @@ import ( "scrumlr.io/server/common" "scrumlr.io/server/common/dto" "scrumlr.io/server/identifiers" + "scrumlr.io/server/logger" ) // createBoardReaction creates a new board reaction func (s *Server) createBoardReaction(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) user := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) var body dto.BoardReactionCreateRequest if err := render.Decode(r, &body); err != nil { common.Throw(w, r, common.BadRequestError(err)) + log.Errorw("unable to create board reaction", "err", err) return } diff --git a/server/src/api/board_session_requests.go b/server/src/api/board_session_requests.go index 5c86f78c1a..b1037b3bd0 100644 --- a/server/src/api/board_session_requests.go +++ b/server/src/api/board_session_requests.go @@ -14,14 +14,15 @@ import ( ) func (s *Server) getBoardSessionRequest(w http.ResponseWriter, r *http.Request) { + log := logger.FromContext(r.Context()) board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) userParam := chi.URLParam(r, "user") user, err := uuid.Parse(userParam) if err != nil { + log.Error(err, "unable to parse body", "err", err) common.Throw(w, r, err) return } - // user should only be allowed to get own session request if user != r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) { common.Throw(w, r, common.ForbiddenError(errors.New("not allowed"))) diff --git a/server/src/api/board_sessions.go b/server/src/api/board_sessions.go index d1ec6c21e9..2fa28c0e22 100644 --- a/server/src/api/board_sessions.go +++ b/server/src/api/board_sessions.go @@ -3,6 +3,7 @@ package api import ( "net/http" "scrumlr.io/server/identifiers" + "scrumlr.io/server/logger" "github.com/go-chi/chi/v5" "github.com/go-chi/render" @@ -10,19 +11,15 @@ import ( "scrumlr.io/server/common" "scrumlr.io/server/common/dto" "scrumlr.io/server/database" - "scrumlr.io/server/logger" ) // getBoardSessions get participants func (s *Server) getBoardSessions(w http.ResponseWriter, r *http.Request) { - log := logger.FromRequest(r) - board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) filter := database.BoardSessionFilterTypeFromQueryString(r.URL.Query()) sessions, err := s.sessions.List(r.Context(), board, filter) if err != nil { - log.Errorw("unable to get board sessions", "err", err) common.Throw(w, r, common.InternalServerError) return } @@ -33,10 +30,12 @@ func (s *Server) getBoardSessions(w http.ResponseWriter, r *http.Request) { // getBoardSession get a participant func (s *Server) getBoardSession(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) userParam := chi.URLParam(r, "session") user, err := uuid.Parse(userParam) if err != nil { + log.Errorw("Invalid user id", "err", err) common.Throw(w, r, err) return } @@ -53,17 +52,20 @@ func (s *Server) getBoardSession(w http.ResponseWriter, r *http.Request) { // updateBoardSession updates a participant func (s *Server) updateBoardSession(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) caller := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) userParam := chi.URLParam(r, "session") user, err := uuid.Parse(userParam) if err != nil { + log.Errorw("Invalid user session id", "err", err) http.Error(w, "invalid user session id", http.StatusBadRequest) return } var body dto.BoardSessionUpdateRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("Unable to decode body", "err", err) http.Error(w, "unable to parse request body", http.StatusBadRequest) return } @@ -84,10 +86,12 @@ func (s *Server) updateBoardSession(w http.ResponseWriter, r *http.Request) { // updateBoardSessions updates all participants func (s *Server) updateBoardSessions(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) var body dto.BoardSessionsUpdateRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("Unable to decode body", "err", err) http.Error(w, "unable to parse request body", http.StatusBadRequest) return } diff --git a/server/src/api/board_templates.go b/server/src/api/board_templates.go index 1b4f99dbc4..a170517719 100644 --- a/server/src/api/board_templates.go +++ b/server/src/api/board_templates.go @@ -30,18 +30,8 @@ func (s *Server) createBoardTemplate(w http.ResponseWriter, r *http.Request) { return } - c, err := s.boardTemplates.ListTemplateColumns(r.Context(), b.ID) - if err != nil { - common.Throw(w, r, common.BadRequestError(err)) - return - } - - // finally append the columns - b.ColumnTemplates = c - render.Status(r, http.StatusCreated) render.Respond(w, r, b) - } func (s *Server) getBoardTemplate(w http.ResponseWriter, r *http.Request) { @@ -60,7 +50,6 @@ func (s *Server) getBoardTemplate(w http.ResponseWriter, r *http.Request) { render.Status(r, http.StatusOK) render.Respond(w, r, template) - } func (s *Server) getBoardTemplates(w http.ResponseWriter, r *http.Request) { diff --git a/server/src/api/boards.go b/server/src/api/boards.go index f750a5f229..3a33156218 100644 --- a/server/src/api/boards.go +++ b/server/src/api/boards.go @@ -21,11 +21,12 @@ import ( // createBoard creates a new board func (s *Server) createBoard(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) owner := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) - // parse request var body dto.CreateBoardRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("Unable to decode body", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } @@ -63,18 +64,15 @@ func (s *Server) deleteBoard(w http.ResponseWriter, r *http.Request) { } func (s *Server) getBoards(w http.ResponseWriter, r *http.Request) { - log := logger.FromRequest(r) user := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) boardIDs, err := s.boards.GetBoards(r.Context(), user) if err != nil { - log.Errorw("unable to get board ids for this user", "err", err) common.Throw(w, r, common.InternalServerError) return } OverviewBoards, err := s.boards.BoardOverview(r.Context(), boardIDs, user) if err != nil { - log.Errorw("unable to get board overview", "err", err) common.Throw(w, r, common.InternalServerError) return } @@ -121,6 +119,7 @@ func (s *Server) joinBoard(w http.ResponseWriter, r *http.Request) { boardParam := chi.URLParam(r, "id") board, err := uuid.Parse(boardParam) if err != nil { + log.Errorw("Wrong board id", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } @@ -128,7 +127,6 @@ func (s *Server) joinBoard(w http.ResponseWriter, r *http.Request) { exists, err := s.sessions.SessionExists(r.Context(), board, user) if err != nil { - log.Errorw("unable to check preexisting sessions", "err", err) common.Throw(w, r, common.InternalServerError) return } @@ -136,7 +134,6 @@ func (s *Server) joinBoard(w http.ResponseWriter, r *http.Request) { if exists { banned, err := s.sessions.ParticipantBanned(r.Context(), board, user) if err != nil { - log.Errorw("unable to check if participant is banned", "err", err) common.Throw(w, r, common.InternalServerError) return } @@ -164,7 +161,6 @@ func (s *Server) joinBoard(w http.ResponseWriter, r *http.Request) { if b.AccessPolicy == types.AccessPolicyPublic { _, err := s.sessions.Create(r.Context(), board, user) if err != nil { - log.Errorw("unable to add participant", "err", err) common.Throw(w, r, common.InternalServerError) return } @@ -192,7 +188,6 @@ func (s *Server) joinBoard(w http.ResponseWriter, r *http.Request) { if encodedPassphrase == *b.Passphrase { _, err := s.sessions.Create(r.Context(), board, user) if err != nil { - log.Errorw("unable to create board session", "err", err) common.Throw(w, r, common.InternalServerError) return } @@ -245,10 +240,12 @@ func (s *Server) joinBoard(w http.ResponseWriter, r *http.Request) { // updateBoard updates a board func (s *Server) updateBoard(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) boardId := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) var body dto.BoardUpdateRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("Unable to decode body", "err", err) http.Error(w, "unable to parse request body", http.StatusBadRequest) return } @@ -265,10 +262,12 @@ func (s *Server) updateBoard(w http.ResponseWriter, r *http.Request) { } func (s *Server) setTimer(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) boardId := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) var body dto.SetTimerRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("Unable to decode body", "err", err) common.Throw(w, r, err) return } @@ -310,21 +309,21 @@ func (s *Server) exportBoard(w http.ResponseWriter, r *http.Request) { boardId := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) - board, _, sessions, columns, notes, _, votings, _, err := s.boards.FullBoard(r.Context(), boardId) + fullBoard, err := s.boards.FullBoard(r.Context(), boardId) if err != nil { common.Throw(w, r, err) return } visibleColumns := []*dto.Column{} - for _, column := range columns { + for _, column := range fullBoard.Columns { if column.Visible { visibleColumns = append(visibleColumns, column) } } visibleNotes := []*dto.Note{} - for _, note := range notes { + for _, note := range fullBoard.Notes { for _, column := range visibleColumns { if note.Position.Column == column.ID { visibleNotes = append(visibleNotes, note) @@ -341,16 +340,16 @@ func (s *Server) exportBoard(w http.ResponseWriter, r *http.Request) { Notes []*dto.Note `json:"notes"` Votings []*dto.Voting `json:"votings"` }{ - Board: board, - Participants: sessions, + Board: fullBoard.Board, + Participants: fullBoard.BoardSessions, Columns: visibleColumns, Notes: visibleNotes, - Votings: votings, + Votings: fullBoard.Votings, }) return } else if r.Header.Get("Accept") == "text/csv" { header := []string{"note_id", "author_id", "author", "text", "column_id", "column", "rank", "stack"} - for index, voting := range votings { + for index, voting := range fullBoard.Votings { if voting.Status == types.VotingStatusClosed { header = append(header, fmt.Sprintf("voting_%d", index)) } @@ -364,7 +363,7 @@ func (s *Server) exportBoard(w http.ResponseWriter, r *http.Request) { } author := note.Author.String() - for _, session := range sessions { + for _, session := range fullBoard.BoardSessions { if session.User.ID == note.Author { author = session.User.Name } @@ -388,7 +387,7 @@ func (s *Server) exportBoard(w http.ResponseWriter, r *http.Request) { stack, } - for _, voting := range votings { + for _, voting := range fullBoard.Votings { if voting.Status == types.VotingStatusClosed { if voting.VotingResults != nil { resultOnNote = append(resultOnNote, strconv.Itoa(voting.VotingResults.Votes[note.ID].Total)) @@ -415,3 +414,118 @@ func (s *Server) exportBoard(w http.ResponseWriter, r *http.Request) { render.Status(r, http.StatusNotAcceptable) render.Respond(w, r, nil) } + +func (s *Server) importBoard(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) + owner := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) + var body dto.ImportBoardRequest + if err := render.Decode(r, &body); err != nil { + log.Errorw("Could not read body", "err", err) + common.Throw(w, r, common.BadRequestError(err)) + return + } + + body.Board.Owner = owner + + columns := make([]dto.ColumnRequest, 0, len(body.Notes)) + + for _, column := range body.Columns { + columns = append(columns, dto.ColumnRequest{ + Name: column.Name, + Color: column.Color, + Visible: &column.Visible, + Index: &column.Index, + }) + } + b, err := s.boards.Create(r.Context(), dto.CreateBoardRequest{ + Name: body.Board.Name, + Description: body.Board.Description, + AccessPolicy: body.Board.AccessPolicy, + Passphrase: body.Board.Passphrase, + Columns: columns, + Owner: owner, + }) + + if err != nil { + log.Errorw("Could not import board", "err", err) + common.Throw(w, r, err) + return + } + + cols, err := s.boards.ListColumns(r.Context(), b.ID) + if err != nil { + _ = s.boards.Delete(r.Context(), b.ID) + + } + + type ParentChildNotes struct { + Parent dto.Note + Children []dto.Note + } + parentNotes := make(map[uuid.UUID]dto.Note) + childNotes := make(map[uuid.UUID][]dto.Note) + + for _, note := range body.Notes { + if !note.Position.Stack.Valid { + parentNotes[note.ID] = note + } else { + childNotes[note.Position.Stack.UUID] = append(childNotes[note.Position.Stack.UUID], note) + } + } + + var organizedNotes []ParentChildNotes + for parentID, parentNote := range parentNotes { + for i, column := range body.Columns { + if parentNote.Position.Column == column.ID { + + note, err := s.notes.Import(r.Context(), dto.NoteImportRequest{ + Text: parentNote.Text, + Position: dto.NotePosition{ + Column: cols[i].ID, + Stack: uuid.NullUUID{}, + Rank: 0, + }, + Board: b.ID, + User: parentNote.Author, + }) + if err != nil { + _ = s.boards.Delete(r.Context(), b.ID) + common.Throw(w, r, err) + return + } + parentNote = *note + } + } + organizedNotes = append(organizedNotes, ParentChildNotes{ + Parent: parentNote, + Children: childNotes[parentID], + }) + } + + for _, node := range organizedNotes { + for _, note := range node.Children { + _, err := s.notes.Import(r.Context(), dto.NoteImportRequest{ + Text: note.Text, + Board: b.ID, + User: note.Author, + Position: dto.NotePosition{ + Column: node.Parent.Position.Column, + Rank: note.Position.Rank, + Stack: uuid.NullUUID{ + UUID: node.Parent.ID, + Valid: true, + }, + }, + }) + if err != nil { + _ = s.boards.Delete(r.Context(), b.ID) + common.Throw(w, r, err) + return + } + + } + } + + render.Status(r, http.StatusCreated) + render.Respond(w, r, b) +} diff --git a/server/src/api/boards_listen_on_board.go b/server/src/api/boards_listen_on_board.go index 1db8e98104..d1d2e7d31c 100644 --- a/server/src/api/boards_listen_on_board.go +++ b/server/src/api/boards_listen_on_board.go @@ -3,11 +3,12 @@ package api import ( "context" "net/http" + "scrumlr.io/server/identifiers" "github.com/google/uuid" "github.com/gorilla/websocket" - dto2 "scrumlr.io/server/common/dto" + "scrumlr.io/server/common/dto" "scrumlr.io/server/logger" "scrumlr.io/server/realtime" ) @@ -15,69 +16,58 @@ import ( type BoardSubscription struct { subscription chan *realtime.BoardEvent clients map[uuid.UUID]*websocket.Conn - boardParticipants []*dto2.BoardSession - boardSettings *dto2.Board - boardColumns []*dto2.Column - boardNotes []*dto2.Note - boardReactions []*dto2.Reaction + boardParticipants []*dto.BoardSession + boardSettings *dto.Board + boardColumns []*dto.Column + boardNotes []*dto.Note + boardReactions []*dto.Reaction } type InitEvent struct { Type realtime.BoardEventType `json:"type"` - Data EventData `json:"data"` + Data dto.FullBoard `json:"data"` } type EventData struct { - Board *dto2.Board `json:"board"` - Columns []*dto2.Column `json:"columns"` - Notes []*dto2.Note `json:"notes"` - Reactions []*dto2.Reaction `json:"reactions"` - Votings []*dto2.Voting `json:"votings"` - Votes []*dto2.Vote `json:"votes"` - Sessions []*dto2.BoardSession `json:"participants"` - Requests []*dto2.BoardSessionRequest `json:"requests"` + Board *dto.Board `json:"board"` + Columns []*dto.Column `json:"columns"` + Notes []*dto.Note `json:"notes"` + Reactions []*dto.Reaction `json:"reactions"` + Votings []*dto.Voting `json:"votings"` + Votes []*dto.Vote `json:"votes"` + Sessions []*dto.BoardSession `json:"participants"` + Requests []*dto.BoardSessionRequest `json:"requests"` } func (s *Server) openBoardSocket(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) id := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) userID := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) conn, err := s.upgrader.Upgrade(w, r, nil) if err != nil { - logger.FromRequest(r).Errorw("unable to upgrade websocket", + log.Errorw("unable to upgrade websocket", "err", err, "board", id, "user", userID) return } - board, requests, sessions, columns, notes, reactions, votings, votes, err := s.boards.FullBoard(r.Context(), id) + fullBoard, err := s.boards.FullBoard(r.Context(), id) if err != nil { - logger.Get().Errorw("failed to prepare init message", "board", id, "user", userID, "err", err) s.closeBoardSocket(id, userID, conn) return } - initEventData := EventData{ - Board: board, - Columns: columns, - Notes: notes, - Reactions: reactions, - Votings: votings, - Votes: votes, - Sessions: sessions, - Requests: requests, - } - initEvent := InitEvent{ Type: realtime.BoardEventInit, - Data: initEventData, + Data: *fullBoard, } initEvent = eventInitFilter(initEvent, userID) err = conn.WriteJSON(initEvent) if err != nil { - logger.Get().Errorw("failed to send init message", "board", id, "user", userID, "err", err) + log.Errorw("failed to send init message", "board", id, "user", userID, "err", err) s.closeBoardSocket(id, userID, conn) return } @@ -107,7 +97,7 @@ func (s *Server) openBoardSocket(w http.ResponseWriter, r *http.Request) { } } -func (s *Server) listenOnBoard(boardID, userID uuid.UUID, conn *websocket.Conn, initEventData EventData) { +func (s *Server) listenOnBoard(boardID, userID uuid.UUID, conn *websocket.Conn, initEventData dto.FullBoard) { if _, exist := s.boardSubscriptions[boardID]; !exist { s.boardSubscriptions[boardID] = &BoardSubscription{ clients: make(map[uuid.UUID]*websocket.Conn), @@ -116,7 +106,7 @@ func (s *Server) listenOnBoard(boardID, userID uuid.UUID, conn *websocket.Conn, b := s.boardSubscriptions[boardID] b.clients[userID] = conn - b.boardParticipants = initEventData.Sessions + b.boardParticipants = initEventData.BoardSessions b.boardSettings = initEventData.Board b.boardColumns = initEventData.Columns b.boardNotes = initEventData.Notes diff --git a/server/src/api/column_templates.go b/server/src/api/column_templates.go new file mode 100644 index 0000000000..6411bc33b0 --- /dev/null +++ b/server/src/api/column_templates.go @@ -0,0 +1,103 @@ +package api + +import ( + "net/http" + "scrumlr.io/server/logger" + + "github.com/go-chi/render" + "github.com/google/uuid" + "scrumlr.io/server/common" + "scrumlr.io/server/common/dto" + "scrumlr.io/server/identifiers" +) + +func (s *Server) createColumnTemplate(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) + boardTemplateId := r.Context().Value(identifiers.BoardTemplateIdentifier).(uuid.UUID) + user := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) + + var body dto.ColumnTemplateRequest + if err := render.Decode(r, &body); err != nil { + log.Errorw("Unable to decode body", "err", err) + http.Error(w, "unable to parse request body", http.StatusBadRequest) + return + } + + body.BoardTemplate = boardTemplateId + body.User = user + + tColumn, err := s.boardTemplates.CreateColumnTemplate(r.Context(), body) + if err != nil { + common.Throw(w, r, common.InternalServerError) + return + } + + render.Status(r, http.StatusCreated) + render.Respond(w, r, tColumn) +} + +func (s *Server) getColumnTemplate(w http.ResponseWriter, r *http.Request) { + boardTemplateId := r.Context().Value(identifiers.BoardTemplateIdentifier).(uuid.UUID) + columnTemplateId := r.Context().Value(identifiers.ColumnTemplateIdentifier).(uuid.UUID) + + columTemplate, err := s.boardTemplates.GetColumnTemplate(r.Context(), boardTemplateId, columnTemplateId) + if err != nil { + common.Throw(w, r, common.InternalServerError) + return + } + + render.Status(r, http.StatusOK) + render.Respond(w, r, columTemplate) +} + +func (s *Server) getColumnTemplates(w http.ResponseWriter, r *http.Request) { + boardTemplateId := r.Context().Value(identifiers.BoardTemplateIdentifier).(uuid.UUID) + + columTemplates, err := s.boardTemplates.ListColumnTemplates(r.Context(), boardTemplateId) + if err != nil { + common.Throw(w, r, common.InternalServerError) + return + } + + render.Status(r, http.StatusOK) + render.Respond(w, r, columTemplates) +} + +func (s *Server) updateColumnTemplate(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) + boardTemplateId := r.Context().Value(identifiers.BoardTemplateIdentifier).(uuid.UUID) + columnTemplateId := r.Context().Value(identifiers.ColumnTemplateIdentifier).(uuid.UUID) + + var body dto.ColumnTemplateUpdateRequest + if err := render.Decode(r, &body); err != nil { + log.Errorw("Unable to decode body", "err", err) + http.Error(w, "unable to parse request body", http.StatusBadRequest) + return + } + + body.BoardTemplate = boardTemplateId + body.ID = columnTemplateId + + tColumn, err := s.boardTemplates.UpdateColumnTemplate(r.Context(), body) + if err != nil { + http.Error(w, "unable to update template column", http.StatusInternalServerError) + return + } + + render.Status(r, http.StatusOK) + render.Respond(w, r, tColumn) +} + +func (s *Server) deleteColumnTemplate(w http.ResponseWriter, r *http.Request) { + boardTemplateId := r.Context().Value(identifiers.BoardTemplateIdentifier).(uuid.UUID) + columnTemplateId := r.Context().Value(identifiers.ColumnTemplateIdentifier).(uuid.UUID) + user := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) + + if err := s.boardTemplates.DeleteColumnTemplate(r.Context(), boardTemplateId, columnTemplateId, user); err != nil { + http.Error(w, "unable to delete column template", http.StatusInternalServerError) + return + } + + render.Status(r, http.StatusNoContent) + render.Respond(w, r, nil) +} diff --git a/server/src/api/columns.go b/server/src/api/columns.go index de096a49d6..762c5ef5d5 100644 --- a/server/src/api/columns.go +++ b/server/src/api/columns.go @@ -20,6 +20,7 @@ func (s *Server) createColumn(w http.ResponseWriter, r *http.Request) { var body dto.ColumnRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("Unable to decode body", "err", err) http.Error(w, "unable to parse request body", http.StatusBadRequest) return } @@ -28,7 +29,6 @@ func (s *Server) createColumn(w http.ResponseWriter, r *http.Request) { body.User = user column, err := s.boards.CreateColumn(r.Context(), body) if err != nil { - log.Errorw("unable to create column", "err", err) common.Throw(w, r, common.InternalServerError) return } @@ -58,11 +58,13 @@ func (s *Server) deleteColumn(w http.ResponseWriter, r *http.Request) { // updateColumn updates a column func (s *Server) updateColumn(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) columnId := r.Context().Value(identifiers.ColumnIdentifier).(uuid.UUID) var body dto.ColumnUpdateRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("Unable to decode body", "err", err) http.Error(w, "unable to parse request body", http.StatusBadRequest) return } @@ -97,13 +99,10 @@ func (s *Server) getColumn(w http.ResponseWriter, r *http.Request) { // getColumns get all columns func (s *Server) getColumns(w http.ResponseWriter, r *http.Request) { - log := logger.FromRequest(r) - board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) columns, err := s.boards.ListColumns(r.Context(), board) if err != nil { - log.Errorw("unable to get columns", "err", err) common.Throw(w, r, common.InternalServerError) return } diff --git a/server/src/api/context.go b/server/src/api/context.go index c1175c568a..bcedb9739b 100644 --- a/server/src/api/context.go +++ b/server/src/api/context.go @@ -260,7 +260,7 @@ func (s *Server) BoardTemplateContext(next http.Handler) http.Handler { func (s *Server) BoardTemplateRateLimiter(next http.Handler) http.Handler { // Initialize the rate limiter limiter := httprate.Limit( - 15, + 20, 1*time.Second, httprate.WithKeyFuncs(httprate.KeyByIP), httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) { @@ -279,3 +279,17 @@ func (s *Server) BoardTemplateRateLimiter(next http.Handler) http.Handler { limiter(next).ServeHTTP(w, r) }) } + +func (s *Server) ColumnTemplateContext(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + columnTemplateParam := chi.URLParam(r, "columnTemplate") + columnTemplate, err := uuid.Parse(columnTemplateParam) + if err != nil { + common.Throw(w, r, common.BadRequestError(errors.New("invalid column id"))) + return + } + + columnTemplateContext := context.WithValue(r.Context(), identifiers.ColumnTemplateIdentifier, columnTemplate) + next.ServeHTTP(w, r.WithContext(columnTemplateContext)) + }) +} diff --git a/server/src/api/event_filter.go b/server/src/api/event_filter.go index 2bf8cfaf86..a0dbf19ea8 100644 --- a/server/src/api/event_filter.go +++ b/server/src/api/event_filter.go @@ -2,7 +2,6 @@ package api import ( "encoding/json" - "github.com/google/uuid" "scrumlr.io/server/common/dto" "scrumlr.io/server/database/types" @@ -96,6 +95,21 @@ func parseParticipantUpdated(data interface{}) (*dto.BoardSession, error) { return ret, nil } +func parseVotesDeleted(data interface{}) ([]*dto.Vote, error) { + var ret []*dto.Vote + + b, err := json.Marshal(data) + if err != nil { + return nil, err + } + err = json.Unmarshal(b, &ret) + if err != nil { + return nil, err + } + return ret, nil + +} + func filterColumns(eventColumns []*dto.Column) []*dto.Column { var visibleColumns = make([]*dto.Column, 0, len(eventColumns)) for _, column := range eventColumns { @@ -108,7 +122,7 @@ func filterColumns(eventColumns []*dto.Column) []*dto.Column { } func filterNotes(eventNotes []*dto.Note, userID uuid.UUID, boardSettings *dto.Board, columns []*dto.Column) []*dto.Note { - var visibleNotes = make([]*dto.Note, 0, len(eventNotes)) + var visibleNotes = make([]*dto.Note, 0) for _, note := range eventNotes { for _, column := range columns { if (note.Position.Column == column.ID) && column.Visible { @@ -306,46 +320,87 @@ func (boardSubscription *BoardSubscription) eventFilter(event *realtime.BoardEve } } + if event.Type == realtime.BoardEventVotesDeleted { + //filter deleted votes after user + votes, err := parseVotesDeleted(event.Data) + if err != nil { + logger.Get().Errorw("unable to parse deleteVotes in event filter", "board", boardSubscription.boardSettings.ID, "session", userID, "err", err) + } + userVotes := make([]*dto.Vote, 0) + for _, v := range votes { + if v.User == userID { + userVotes = append(userVotes, v) + } + } + + ret := realtime.BoardEvent{ + Type: event.Type, + Data: userVotes, + } + + return &ret + } + // returns, if no filter match occured return event } func eventInitFilter(event InitEvent, clientID uuid.UUID) InitEvent { - isMod := isModerator(clientID, event.Data.Sessions) + isMod := isModerator(clientID, event.Data.BoardSessions) + + // filter to only respond with the latest voting and its votes + if len(event.Data.Votings) != 0 { + latestVoting := make([]*dto.Voting, 0) + activeNotes := make([]*dto.Vote, 0) + + latestVoting = append(latestVoting, event.Data.Votings[0]) + + for _, v := range event.Data.Votes { + if v.Voting == latestVoting[0].ID { + if latestVoting[0].Status == types.VotingStatusOpen { + if v.User == clientID { + activeNotes = append(activeNotes, v) + } + } else { + activeNotes = append(activeNotes, v) + } + } + } + event.Data.Votings = latestVoting + event.Data.Votes = activeNotes + } + if isMod { return event } retEvent := InitEvent{ Type: event.Type, - Data: EventData{ - Board: event.Data.Board, - Notes: nil, - Reactions: event.Data.Reactions, - Columns: nil, - Votings: event.Data.Votings, - Votes: event.Data.Votes, - Sessions: event.Data.Sessions, - Requests: event.Data.Requests, + Data: dto.FullBoard{ + Board: event.Data.Board, + BoardSessions: event.Data.BoardSessions, + BoardSessionRequests: event.Data.BoardSessionRequests, + Notes: nil, + Reactions: event.Data.Reactions, + Columns: nil, + Votings: event.Data.Votings, + Votes: event.Data.Votes, }, } + // Columns filteredColumns := filterColumns(event.Data.Columns) - - // Notes + // Notes TODO: make to map for easier checks filteredNotes := filterNotes(event.Data.Notes, clientID, event.Data.Board, event.Data.Columns) - + notesMap := make(map[uuid.UUID]*dto.Note) + for _, n := range filteredNotes { + notesMap[n.ID] = n + } // Votes visibleVotes := make([]*dto.Vote, 0) - for _, v := range event.Data.Votes { - for _, n := range filteredNotes { - if v.Note == n.ID { - aVote := dto.Vote{ - Voting: v.Voting, - Note: n.ID, - } - visibleVotes = append(visibleVotes, &aVote) - } + for _, vote := range event.Data.Votes { + if _, exists := notesMap[vote.Note]; exists { + visibleVotes = append(visibleVotes, vote) } } // Votings diff --git a/server/src/api/event_filter_test.go b/server/src/api/event_filter_test.go index 99816036d5..1d599a555b 100644 --- a/server/src/api/event_filter_test.go +++ b/server/src/api/event_filter_test.go @@ -133,14 +133,14 @@ var ( } initEvent = InitEvent{ Type: realtime.BoardEventInit, - Data: EventData{ - Board: &dto.Board{}, - Columns: []*dto.Column{&aSeeableColumn, &aHiddenColumn}, - Notes: []*dto.Note{&aOwnerNote, &aModeratorNote, &aParticipantNote}, - Votings: []*dto.Voting{votingData.Voting}, - Votes: []*dto.Vote{}, - Sessions: boardSessions, - Requests: []*dto.BoardSessionRequest{}, + Data: dto.FullBoard{ + Board: &dto.Board{}, + Columns: []*dto.Column{&aSeeableColumn, &aHiddenColumn}, + Notes: []*dto.Note{&aOwnerNote, &aModeratorNote, &aParticipantNote}, + Votings: []*dto.Voting{votingData.Voting}, + Votes: []*dto.Vote{}, + BoardSessions: boardSessions, + BoardSessionRequests: []*dto.BoardSessionRequest{}, }, } ) @@ -381,14 +381,14 @@ func testInitFilterAsParticipant(t *testing.T) { } expectedInitEvent := InitEvent{ Type: realtime.BoardEventInit, - Data: EventData{ - Board: &dto.Board{}, - Columns: []*dto.Column{&aSeeableColumn}, - Notes: []*dto.Note{&aParticipantNote}, - Votings: []*dto.Voting{&expectedVoting}, - Votes: []*dto.Vote{}, - Sessions: boardSessions, - Requests: []*dto.BoardSessionRequest{}, + Data: dto.FullBoard{ + Board: &dto.Board{}, + Columns: []*dto.Column{&aSeeableColumn}, + Notes: []*dto.Note{&aParticipantNote}, + Votings: []*dto.Voting{&expectedVoting}, + Votes: []*dto.Vote{}, + BoardSessions: boardSessions, + BoardSessionRequests: []*dto.BoardSessionRequest{}, }, } returnedInitEvent := eventInitFilter(initEvent, participantBoardSession.User.ID) diff --git a/server/src/api/feedback.go b/server/src/api/feedback.go index 0c8047ea05..27531540c9 100644 --- a/server/src/api/feedback.go +++ b/server/src/api/feedback.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/go-chi/render" "net/http" + "scrumlr.io/server/logger" ) type FeedbackType string @@ -36,8 +37,10 @@ type FeedbackRequest struct { } func (s *Server) createFeedback(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) var body FeedbackRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("Unable to decode body", "err", err) w.WriteHeader(http.StatusBadRequest) return } diff --git a/server/src/api/info.go b/server/src/api/info.go index e61d06b87d..c81c9dcf18 100644 --- a/server/src/api/info.go +++ b/server/src/api/info.go @@ -35,6 +35,9 @@ func (s *Server) getServerInfo(w http.ResponseWriter, r *http.Request) { if s.auth.Exists(types.AccountTypeApple) { info.AuthProvider = append(info.AuthProvider, types.AccountTypeApple) } + if s.auth.Exists(types.AccountTypeOIDC) { + info.AuthProvider = append(info.AuthProvider, types.AccountTypeOIDC) + } info.ServerTime = time.Now() diff --git a/server/src/api/json_parse_test.go b/server/src/api/json_parse_test.go index 745aff3366..f93888dd54 100644 --- a/server/src/api/json_parse_test.go +++ b/server/src/api/json_parse_test.go @@ -4,6 +4,7 @@ import ( "net/http" "net/http/httptest" "scrumlr.io/server/identifiers" + "scrumlr.io/server/logger" "strings" "testing" @@ -74,15 +75,21 @@ func (suite *JSONErrTestSuite) TestJSONErrs() { handler: func(s *Server) func(w http.ResponseWriter, r *http.Request) { return s.updateBoardSessionRequest }, }, } + for _, tt := range tests { suite.Run(tt.name, func() { s := new(Server) - mockUUID, _ := uuid.NewRandom() + //loggerConfig := zap.NewNop() // Use a no-op logger for testing + //_logger := loggerConfig.Sugar() + + mockUUID := uuid.New() req := NewTestRequestBuilder("POST", "/", strings.NewReader(`{ "id": %s - }`)). - AddToContext(identifiers.BoardIdentifier, mockUUID). + }`)) + + req.req = logger.InitTestLoggerRequest(req.Request()) + req.AddToContext(identifiers.BoardIdentifier, mockUUID). AddToContext(identifiers.UserIdentifier, mockUUID). AddToContext(identifiers.NoteIdentifier, mockUUID). AddToContext(identifiers.ColumnIdentifier, mockUUID). diff --git a/server/src/api/login.go b/server/src/api/login.go index b3897c036c..0c163b3641 100644 --- a/server/src/api/login.go +++ b/server/src/api/login.go @@ -28,13 +28,13 @@ func (s *Server) signInAnonymously(w http.ResponseWriter, r *http.Request) { var body AnonymousSignUpRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("unable to decode body", "err", err) w.WriteHeader(http.StatusBadRequest) return } user, err := s.users.LoginAnonymous(r.Context(), body.Name) if err != nil { - log.Errorw("could not create user", "req", body, "err", err) common.Throw(w, r, common.InternalServerError) return } @@ -85,19 +85,34 @@ func (s *Server) verifyAuthProviderCallback(w http.ResponseWriter, r *http.Reque return } - provider := strings.ToUpper(externalUser.Provider) + provider, err := types.NewAccountType(externalUser.Provider) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Errorw("unsupported user provider", "err", err) + return + } + + userInfo, err := s.auth.ExtractUserInformation(provider, &externalUser) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Errorw("insufficient user information from external auth source", "err", err) + return + } + var internalUser *dto.User switch provider { - case (string)(types.AccountTypeGoogle): - internalUser, err = s.users.CreateGoogleUser(r.Context(), externalUser.UserID, externalUser.NickName, externalUser.AvatarURL) - case (string)(types.AccountTypeGitHub): - internalUser, err = s.users.CreateGitHubUser(r.Context(), externalUser.UserID, externalUser.NickName, externalUser.AvatarURL) - case (string)(types.AccountTypeMicrosoft): - internalUser, err = s.users.CreateMicrosoftUser(r.Context(), externalUser.UserID, externalUser.NickName, externalUser.AvatarURL) - case (string)(types.AccountTypeAzureAd): - internalUser, err = s.users.CreateAzureAdUser(r.Context(), externalUser.UserID, externalUser.NickName, externalUser.AvatarURL) - case (string)(types.AccountTypeApple): - internalUser, err = s.users.CreateAppleUser(r.Context(), externalUser.UserID, externalUser.NickName, externalUser.AvatarURL) + case types.AccountTypeGoogle: + internalUser, err = s.users.CreateGoogleUser(r.Context(), userInfo.Ident, userInfo.Name, userInfo.AvatarURL) + case types.AccountTypeGitHub: + internalUser, err = s.users.CreateGitHubUser(r.Context(), userInfo.Ident, userInfo.Name, userInfo.AvatarURL) + case types.AccountTypeMicrosoft: + internalUser, err = s.users.CreateMicrosoftUser(r.Context(), userInfo.Ident, userInfo.Name, userInfo.AvatarURL) + case types.AccountTypeAzureAd: + internalUser, err = s.users.CreateAzureAdUser(r.Context(), userInfo.Ident, userInfo.Name, userInfo.AvatarURL) + case types.AccountTypeApple: + internalUser, err = s.users.CreateAppleUser(r.Context(), userInfo.Ident, userInfo.Name, userInfo.AvatarURL) + case types.AccountTypeOIDC: + internalUser, err = s.users.CreateOIDCUser(r.Context(), userInfo.Ident, userInfo.Name, userInfo.AvatarURL) } if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/server/src/api/notes.go b/server/src/api/notes.go index a23f5e8649..29c987a406 100644 --- a/server/src/api/notes.go +++ b/server/src/api/notes.go @@ -8,15 +8,18 @@ import ( "scrumlr.io/server/common" "scrumlr.io/server/common/dto" "scrumlr.io/server/identifiers" + "scrumlr.io/server/logger" ) // createNote creates a new note func (s *Server) createNote(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) user := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) var body dto.NoteCreateRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("unable to decode body", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } @@ -68,11 +71,13 @@ func (s *Server) getNotes(w http.ResponseWriter, r *http.Request) { // updateNote updates a note func (s *Server) updateNote(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) boardID := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) noteID := r.Context().Value(identifiers.NoteIdentifier).(uuid.UUID) var body dto.NoteUpdateRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("unable to decode body", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } @@ -91,9 +96,11 @@ func (s *Server) updateNote(w http.ResponseWriter, r *http.Request) { // deleteNote deletes a note func (s *Server) deleteNote(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) note := r.Context().Value(identifiers.NoteIdentifier).(uuid.UUID) var body dto.NoteDeleteRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("unable to decode body", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } diff --git a/server/src/api/notes_test.go b/server/src/api/notes_test.go index e7343dc0c1..ec4cecda5c 100644 --- a/server/src/api/notes_test.go +++ b/server/src/api/notes_test.go @@ -14,6 +14,7 @@ import ( "scrumlr.io/server/common/dto" "scrumlr.io/server/common/filter" "scrumlr.io/server/identifiers" + "scrumlr.io/server/logger" "scrumlr.io/server/services" "strings" "testing" @@ -187,8 +188,11 @@ func (suite *NotesTestSuite) TestCreateNote() { req := NewTestRequestBuilder("POST", "/", strings.NewReader(fmt.Sprintf(`{ "column": "%s", "text" : "%s" - }`, colId.String(), testText))). - AddToContext(identifiers.BoardIdentifier, boardId). + }`, colId.String(), testText))) + + req.req = logger.InitTestLoggerRequest(req.Request()) + + req.AddToContext(identifiers.BoardIdentifier, boardId). AddToContext(identifiers.UserIdentifier, userId) rr := httptest.NewRecorder() @@ -313,6 +317,7 @@ func (suite *NotesTestSuite) TestDeleteNote() { } req := NewTestRequestBuilder("DELETE", fmt.Sprintf("/notes/%s", noteID.String()), strings.NewReader(`{"deleteStack": false}`)) + req.req = logger.InitTestLoggerRequest(req.Request()) rctx := chi.NewRouteContext() rctx.URLParams.Add("id", boardID.String()) req.AddToContext(chi.RouteCtxKey, rctx) diff --git a/server/src/api/reactions.go b/server/src/api/reactions.go index ea59a020ec..1b58742380 100644 --- a/server/src/api/reactions.go +++ b/server/src/api/reactions.go @@ -7,6 +7,7 @@ import ( "scrumlr.io/server/common" "scrumlr.io/server/common/dto" "scrumlr.io/server/identifiers" + "scrumlr.io/server/logger" ) func (s *Server) getReaction(w http.ResponseWriter, r *http.Request) { @@ -36,11 +37,13 @@ func (s *Server) getReactions(w http.ResponseWriter, r *http.Request) { } func (s *Server) createReaction(w http.ResponseWriter, r *http.Request) { + log := logger.FromContext(r.Context()) board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) user := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) var body dto.ReactionCreateRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("unable to decode body", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } @@ -73,11 +76,13 @@ func (s *Server) removeReaction(w http.ResponseWriter, r *http.Request) { } func (s *Server) updateReaction(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) user := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) id := r.Context().Value(identifiers.ReactionIdentifier).(uuid.UUID) var body dto.ReactionUpdateTypeRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("unable to decode body", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } diff --git a/server/src/api/request_builder.go b/server/src/api/request_builder.go index 9772fbb5cd..d20f2c5be4 100644 --- a/server/src/api/request_builder.go +++ b/server/src/api/request_builder.go @@ -27,4 +27,5 @@ func (b *TestRequestBuilder) AddToContext(key, val interface{}) *TestRequestBuil func (b *TestRequestBuilder) Request() *http.Request { return b.req.Clone(b.req.Context()) + } diff --git a/server/src/api/router.go b/server/src/api/router.go index 2481b01fd3..83d72063a5 100644 --- a/server/src/api/router.go +++ b/server/src/api/router.go @@ -1,23 +1,24 @@ package api import ( + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/markbates/goth/gothic" "net/http" + "os" "time" "github.com/go-chi/cors" "github.com/go-chi/httprate" - "github.com/go-chi/jwtauth/v5" "github.com/go-chi/render" "github.com/google/uuid" + gorillaSessions "github.com/gorilla/sessions" "github.com/gorilla/websocket" "scrumlr.io/server/auth" "scrumlr.io/server/logger" "scrumlr.io/server/realtime" "scrumlr.io/server/services" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" ) type Server struct { @@ -43,7 +44,9 @@ type Server struct { boardSubscriptions map[uuid.UUID]*BoardSubscription boardSessionRequestSubscriptions map[uuid.UUID]*BoardSessionRequestSubscription - anonymousLoginDisabled bool + // note: if more options come with time, it might be sensible to wrap them into a struct + anonymousLoginDisabled bool + experimentalFileSystemStore bool } func New( @@ -65,6 +68,7 @@ func New( verbose bool, checkOrigin bool, anonymousLoginDisabled bool, + experimentalFileSystemStore bool, ) chi.Router { r := chi.NewRouter() r.Use(middleware.Recoverer) @@ -106,7 +110,8 @@ func New( boardReactions: boardReactions, boardTemplates: boardTemplates, - anonymousLoginDisabled: anonymousLoginDisabled, + anonymousLoginDisabled: anonymousLoginDisabled, + experimentalFileSystemStore: experimentalFileSystemStore, } // initialize websocket upgrader with origin check depending on options @@ -115,6 +120,16 @@ func New( WriteBufferSize: 1024, } + // if enabled, this experimental feature allows for larger session cookies *during OAuth authentication* by storing them in a file store. + // this might be required when using some OIDC providers which exceed the 4KB limit. + // see https://github.com/markbates/goth/pull/141 + if s.experimentalFileSystemStore { + logger.Get().Infow("using experimental file system store") + store := gorillaSessions.NewFilesystemStore(os.TempDir(), []byte("scrumlr.io")) + store.MaxLength(0x8000) // 32KB should be plenty of space + gothic.Store = store + } + if checkOrigin { s.upgrader.CheckOrigin = nil } else { @@ -154,19 +169,35 @@ func (s *Server) publicRoutes(r chi.Router) chi.Router { func (s *Server) protectedRoutes(r chi.Router) { r.Group(func(r chi.Router) { r.Use(s.auth.Verifier()) - r.Use(jwtauth.Authenticator) + r.Use(s.auth.Authenticator()) r.Use(auth.AuthContext) r.With(s.BoardTemplateRateLimiter).Post("/templates", s.createBoardTemplate) r.With(s.BoardTemplateRateLimiter).Get("/templates", s.getBoardTemplates) r.Route("/templates/{id}", func(r chi.Router) { r.Use(s.BoardTemplateRateLimiter) - r.With(s.BoardTemplateContext).Get("/", s.getBoardTemplate) - r.With(s.BoardTemplateContext).Put("/", s.updateBoardTemplate) - r.With(s.BoardTemplateContext).Delete("/", s.deleteBoardTemplate) + r.Use(s.BoardTemplateContext) + + r.Get("/", s.getBoardTemplate) + r.Put("/", s.updateBoardTemplate) + r.Delete("/", s.deleteBoardTemplate) + + r.Route("/columns", func(r chi.Router) { + r.Post("/", s.createColumnTemplate) + r.Get("/", s.getColumnTemplates) + + r.Route("/{columnTemplate}", func(r chi.Router) { + r.Use(s.ColumnTemplateContext) + + r.Get("/", s.getColumnTemplate) + r.Put("/", s.updateColumnTemplate) + r.Delete("/", s.deleteColumnTemplate) + }) + }) }) r.Post("/boards", s.createBoard) + r.Post("/import", s.importBoard) r.Get("/boards", s.getBoards) r.Route("/boards/{id}", func(r chi.Router) { r.With(s.BoardParticipantContext).Get("/", s.getBoard) diff --git a/server/src/api/users.go b/server/src/api/users.go index 376d24e1ce..9f8b3f6484 100644 --- a/server/src/api/users.go +++ b/server/src/api/users.go @@ -26,11 +26,11 @@ func (s *Server) getUser(w http.ResponseWriter, r *http.Request) { func (s *Server) updateUser(w http.ResponseWriter, r *http.Request) { log := logger.FromRequest(r) - user := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) var body dto.UserUpdateRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("unable to decode body", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } @@ -39,7 +39,6 @@ func (s *Server) updateUser(w http.ResponseWriter, r *http.Request) { updatedUser, err := s.users.Update(r.Context(), body) if err != nil { - log.Errorw("failed to update user", "err", err) common.Throw(w, r, common.InternalServerError) return } diff --git a/server/src/api/votes.go b/server/src/api/votes.go index d171f7da6b..f84d7e4ed3 100644 --- a/server/src/api/votes.go +++ b/server/src/api/votes.go @@ -15,12 +15,12 @@ import ( // addVote adds a vote to the currently open voting session func (s *Server) addVote(w http.ResponseWriter, r *http.Request) { log := logger.FromRequest(r) - board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) user := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) var body dto.VoteRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("unable to decode body", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } @@ -48,6 +48,7 @@ func (s *Server) removeVote(w http.ResponseWriter, r *http.Request) { var body dto.VoteRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("unable to decode body", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } @@ -67,6 +68,7 @@ func (s *Server) removeVote(w http.ResponseWriter, r *http.Request) { } func (s *Server) getVotes(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) user := r.Context().Value(identifiers.UserIdentifier).(uuid.UUID) @@ -79,6 +81,7 @@ func (s *Server) getVotes(w http.ResponseWriter, r *http.Request) { if votingQuery != "" { voting, err := uuid.Parse(votingQuery) if err != nil { + log.Errorw("unable to decode body", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } @@ -89,6 +92,7 @@ func (s *Server) getVotes(w http.ResponseWriter, r *http.Request) { if noteQuery != "" { note, err := uuid.Parse(noteQuery) if err != nil { + log.Errorw("unable to decode body", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } diff --git a/server/src/api/votes_test.go b/server/src/api/votes_test.go index 91ac63fba1..7ada9f1bd8 100644 --- a/server/src/api/votes_test.go +++ b/server/src/api/votes_test.go @@ -8,6 +8,7 @@ import ( "scrumlr.io/server/common" "scrumlr.io/server/common/dto" "scrumlr.io/server/identifiers" + "scrumlr.io/server/logger" "strings" "testing" @@ -71,8 +72,9 @@ func (suite *VoteTestSuite) TestAddVote() { req := NewTestRequestBuilder("POST", "/", strings.NewReader(fmt.Sprintf(`{ "note": "%s" - }`, noteId.String()))). - AddToContext(identifiers.BoardIdentifier, boardId). + }`, noteId.String()))) + req.req = logger.InitTestLoggerRequest(req.Request()) + req.AddToContext(identifiers.BoardIdentifier, boardId). AddToContext(identifiers.UserIdentifier, userId) rr := httptest.NewRecorder() diff --git a/server/src/api/votings.go b/server/src/api/votings.go index f0f6cf3c50..e59c0c0981 100644 --- a/server/src/api/votings.go +++ b/server/src/api/votings.go @@ -6,6 +6,7 @@ import ( "scrumlr.io/server/common" "scrumlr.io/server/common/dto" "scrumlr.io/server/identifiers" + "scrumlr.io/server/logger" "github.com/go-chi/render" "github.com/google/uuid" @@ -13,10 +14,12 @@ import ( // createVoting creates a new voting session func (s *Server) createVoting(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) var body dto.VotingCreateRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("Unable to decode body", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } @@ -39,11 +42,13 @@ func (s *Server) createVoting(w http.ResponseWriter, r *http.Request) { // updateVoting updates a voting session func (s *Server) updateVoting(w http.ResponseWriter, r *http.Request) { + log := logger.FromRequest(r) board := r.Context().Value(identifiers.BoardIdentifier).(uuid.UUID) id := r.Context().Value(identifiers.VotingIdentifier).(uuid.UUID) var body dto.VotingUpdateRequest if err := render.Decode(r, &body); err != nil { + log.Errorw("Unable to decode body", "err", err) common.Throw(w, r, common.BadRequestError(err)) return } @@ -53,6 +58,7 @@ func (s *Server) updateVoting(w http.ResponseWriter, r *http.Request) { voting, err := s.votings.Update(r.Context(), body) if err != nil { + common.Throw(w, r, err) return } diff --git a/server/src/api/votings_test.go b/server/src/api/votings_test.go index f8f1f61420..b53430f57c 100644 --- a/server/src/api/votings_test.go +++ b/server/src/api/votings_test.go @@ -9,6 +9,7 @@ import ( "scrumlr.io/server/common/dto" "scrumlr.io/server/common/filter" "scrumlr.io/server/identifiers" + "scrumlr.io/server/logger" "scrumlr.io/server/services" "strings" "testing" @@ -106,7 +107,9 @@ func (suite *VotingTestSuite) TestCreateVoting() { "voteLimit": 4, "allowMultipleVotes": false, "showVotesOfOthers": false - }`)).AddToContext(identifiers.BoardIdentifier, boardId) + }`)) + req.req = logger.InitTestLoggerRequest(req.Request()) + req.AddToContext(identifiers.BoardIdentifier, boardId) rr := httptest.NewRecorder() s.createVoting(rr, req.Request()) @@ -155,8 +158,9 @@ func (suite *VotingTestSuite) TestUpdateVoting() { req := NewTestRequestBuilder("PUT", "/", strings.NewReader(`{ "status": "CLOSED" - }`)). - AddToContext(identifiers.BoardIdentifier, boardId). + }`)) + req.req = logger.InitTestLoggerRequest(req.Request()) + req.AddToContext(identifiers.BoardIdentifier, boardId). AddToContext(identifiers.VotingIdentifier, votingId) rr := httptest.NewRecorder() diff --git a/server/src/auth/auth.go b/server/src/auth/auth.go index 9d06d97fe1..3f43158058 100644 --- a/server/src/auth/auth.go +++ b/server/src/auth/auth.go @@ -1,230 +1,294 @@ package auth import ( - "crypto/ecdsa" - "crypto/rand" - "encoding/base64" - "errors" - "fmt" - "io" - "math" - "net/http" - "strings" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/jwtauth/v5" - "github.com/google/uuid" - "github.com/lestrrat-go/jwx/v2/jwt" - "github.com/markbates/goth" - "github.com/markbates/goth/gothic" - "github.com/markbates/goth/providers/apple" - "github.com/markbates/goth/providers/azureadv2" - "github.com/markbates/goth/providers/github" - "github.com/markbates/goth/providers/google" - "github.com/markbates/goth/providers/microsoftonline" - "golang.org/x/crypto/ssh" - "scrumlr.io/server/auth/devkeys" - "scrumlr.io/server/common" - "scrumlr.io/server/database" - "scrumlr.io/server/database/types" - "scrumlr.io/server/logger" + "crypto/ecdsa" + "crypto/rand" + "encoding/base64" + "errors" + "fmt" + "io" + "math" + "net/http" + "strings" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/jwtauth/v5" + "github.com/google/uuid" + "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/markbates/goth" + "github.com/markbates/goth/gothic" + "github.com/markbates/goth/providers/apple" + "github.com/markbates/goth/providers/azureadv2" + "github.com/markbates/goth/providers/github" + "github.com/markbates/goth/providers/google" + "github.com/markbates/goth/providers/microsoftonline" + oidc "github.com/markbates/goth/providers/openidConnect" + "golang.org/x/crypto/ssh" + "scrumlr.io/server/auth/devkeys" + "scrumlr.io/server/common" + "scrumlr.io/server/database" + "scrumlr.io/server/database/types" + "scrumlr.io/server/logger" ) type Auth interface { - Sign(map[string]interface{}) (string, error) - Verifier() func(http.Handler) http.Handler - Exists(accountType types.AccountType) bool + Sign(map[string]interface{}) (string, error) + Verifier() func(http.Handler) http.Handler + Authenticator() func(http.Handler) http.Handler + Exists(accountType types.AccountType) bool + ExtractUserInformation(types.AccountType, *goth.User) (*UserInformation, error) } type AuthProviderConfiguration struct { - TenantId string - ClientId string - ClientSecret string - RedirectUri string + TenantId string + ClientId string + ClientSecret string + RedirectUri string + DiscoveryUri string + UserIdentScope string + UserNameScope string } type AuthConfiguration struct { - providers map[string]AuthProviderConfiguration - unsafePrivateKey string - privateKey string - unsafeAuth *jwtauth.JWTAuth - auth *jwtauth.JWTAuth - database *database.Database + providers map[string]AuthProviderConfiguration + unsafePrivateKey string + privateKey string + unsafeAuth *jwtauth.JWTAuth + auth *jwtauth.JWTAuth + database *database.Database +} + +type UserInformation struct { + Provider types.AccountType + Ident, Name, AvatarURL string } -func NewAuthConfiguration(providers map[string]AuthProviderConfiguration, unsafePrivateKey, privateKey string, database *database.Database) Auth { - a := new(AuthConfiguration) - a.providers = providers - a.unsafePrivateKey = unsafePrivateKey - a.database = database - a.privateKey = privateKey - a.initializeProviders() - a.initializeJWTAuth() +func NewAuthConfiguration(providers map[string]AuthProviderConfiguration, unsafePrivateKey, privateKey string, database *database.Database) (Auth, error) { + a := new(AuthConfiguration) + a.providers = providers + a.unsafePrivateKey = unsafePrivateKey + a.database = database + a.privateKey = privateKey + if err := a.initializeProviders(); err != nil { + return nil, err + } + if err := a.initializeJWTAuth(); err != nil { + return nil, err + } - return a + return a, nil } -func (a *AuthConfiguration) initializeProviders() { - providers := []goth.Provider{} - if provider, ok := a.providers[(string)(types.AccountTypeGoogle)]; ok { - p := google.New( - provider.ClientId, - provider.ClientSecret, - provider.RedirectUri, - "openid", - "profile", - ) - p.SetName(strings.ToLower((string)(types.AccountTypeGoogle))) - providers = append(providers, p) - } - if provider, ok := a.providers[(string)(types.AccountTypeGitHub)]; ok { - p := github.New( - provider.ClientId, - provider.ClientSecret, - provider.RedirectUri, - "user", - ) - p.SetName(strings.ToLower((string)(types.AccountTypeGitHub))) - providers = append(providers, p) - } - if provider, ok := a.providers[(string)(types.AccountTypeMicrosoft)]; ok { - p := microsoftonline.New( - provider.ClientId, - provider.ClientSecret, - provider.RedirectUri, - "User.Read", - ) - p.SetName(strings.ToLower((string)(types.AccountTypeMicrosoft))) - providers = append(providers, p) - } - if provider, ok := a.providers[(string)(types.AccountTypeAzureAd)]; ok { - p := azureadv2.New( - provider.ClientId, - provider.ClientSecret, - provider.RedirectUri, - azureadv2.ProviderOptions{ - Tenant: azureadv2.TenantType(provider.TenantId), - Scopes: []azureadv2.ScopeType{"User.Read"}, - }, - ) - p.SetName(strings.ToLower((string)(types.AccountTypeAzureAd))) - providers = append(providers, p) - } - - if provider, ok := a.providers[(string)(types.AccountTypeApple)]; ok { - providers = append(providers, apple.New( - provider.ClientId, - provider.ClientSecret, - provider.RedirectUri, - nil, - apple.ScopeName, - apple.ScopeEmail, - )) - } - goth.UseProviders(providers...) - gothic.GetProviderName = func(r *http.Request) (string, error) { - return chi.URLParam(r, "provider"), nil - } - gothic.SetState = func(r *http.Request) string { - nonceBytes := make([]byte, 64) - _, err := io.ReadFull(rand.Reader, nonceBytes) - if err != nil { - panic("gothic: source of randomness unavailable: " + err.Error()) - } - nonce := base64.URLEncoding.EncodeToString(nonceBytes) - - state := r.URL.Query().Get("state") - if len(state) > 0 { - return fmt.Sprintf("%s__%s", nonce, state) - } - - return nonce - } +func (a *AuthConfiguration) initializeProviders() error { + providers := []goth.Provider{} + if provider, ok := a.providers[(string)(types.AccountTypeGoogle)]; ok { + p := google.New( + provider.ClientId, + provider.ClientSecret, + provider.RedirectUri, + "openid", + "profile", + ) + p.SetName(strings.ToLower((string)(types.AccountTypeGoogle))) + providers = append(providers, p) + } + if provider, ok := a.providers[(string)(types.AccountTypeGitHub)]; ok { + p := github.New( + provider.ClientId, + provider.ClientSecret, + provider.RedirectUri, + "user", + ) + p.SetName(strings.ToLower((string)(types.AccountTypeGitHub))) + providers = append(providers, p) + } + if provider, ok := a.providers[(string)(types.AccountTypeMicrosoft)]; ok { + p := microsoftonline.New( + provider.ClientId, + provider.ClientSecret, + provider.RedirectUri, + "User.Read", + ) + p.SetName(strings.ToLower((string)(types.AccountTypeMicrosoft))) + providers = append(providers, p) + } + if provider, ok := a.providers[(string)(types.AccountTypeAzureAd)]; ok { + p := azureadv2.New( + provider.ClientId, + provider.ClientSecret, + provider.RedirectUri, + azureadv2.ProviderOptions{ + Tenant: azureadv2.TenantType(provider.TenantId), + Scopes: []azureadv2.ScopeType{"User.Read"}, + }, + ) + p.SetName(strings.ToLower((string)(types.AccountTypeAzureAd))) + providers = append(providers, p) + } + if provider, ok := a.providers[(string)(types.AccountTypeApple)]; ok { + providers = append(providers, apple.New( + provider.ClientId, + provider.ClientSecret, + provider.RedirectUri, + nil, + apple.ScopeName, + apple.ScopeEmail, + )) + } + if provider, ok := a.providers[(string)(types.AccountTypeOIDC)]; ok { + p, err := oidc.New( + provider.ClientId, + provider.ClientSecret, + provider.RedirectUri, + provider.DiscoveryUri, + provider.UserIdentScope, + provider.UserNameScope, + ) + if err != nil { + logger.Get().Errorw("OIDC provider setup failed", "error", err) + } + + p.SetName(strings.ToLower((string)(types.AccountTypeOIDC))) + providers = append(providers, p) + } + goth.UseProviders(providers...) + gothic.GetProviderName = func(r *http.Request) (string, error) { + return chi.URLParam(r, "provider"), nil + } + gothic.SetState = func(r *http.Request) string { + nonceBytes := make([]byte, 64) + _, err := io.ReadFull(rand.Reader, nonceBytes) + if err != nil { + panic("gothic: source of randomness unavailable: " + err.Error()) + } + nonce := base64.URLEncoding.EncodeToString(nonceBytes) + + state := r.URL.Query().Get("state") + if len(state) > 0 { + return fmt.Sprintf("%s__%s", nonce, state) + } + + return nonce + } + + return nil } func (a *AuthConfiguration) Sign(claims map[string]interface{}) (string, error) { - _, token, err := a.auth.Encode(claims) - return token, err + _, token, err := a.auth.Encode(claims) + return token, err } func (a *AuthConfiguration) Verifier() func(http.Handler) http.Handler { - if a.unsafeAuth != nil { - return func(next http.Handler) http.Handler { - hfn := func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var token jwt.Token - var err error - - if token, err = jwtauth.VerifyRequest(a.unsafeAuth, r, jwtauth.TokenFromCookie); err == nil { - // check if user tries to authenticate by a prior authentication key - // attempt to migrate JWT to new key - userID := token.PrivateClaims()["id"].(string) - var user uuid.UUID - user, err = uuid.Parse(userID) - - if err == nil { - var ok bool - if ok, err = a.database.IsUserAvailableForKeyMigration(user); ok { - // prepare new JWT - tokenString, _ := a.Sign(map[string]interface{}{"id": user}) - cookie := http.Cookie{Name: "jwt", Value: tokenString, Path: "/", HttpOnly: true, MaxAge: math.MaxInt32} - common.SealCookie(r, &cookie) - http.SetCookie(w, &cookie) - - // update rotation flag in database for user, ignore errors - _, _ = a.database.SetKeyMigration(user) - } else { - err = errors.New("not permitted to access key rotation") - } - } - } else { - // attempt to verify request by new key - token, err = jwtauth.VerifyRequest(a.auth, r, jwtauth.TokenFromCookie) - } - - ctx = jwtauth.NewContext(ctx, token, err) - next.ServeHTTP(w, r.WithContext(ctx)) - } - return http.HandlerFunc(hfn) - } - } - return jwtauth.Verifier(a.auth) + if a.unsafeAuth != nil { + return func(next http.Handler) http.Handler { + hfn := func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var token jwt.Token + var err error + + if token, err = jwtauth.VerifyRequest(a.unsafeAuth, r, jwtauth.TokenFromCookie); err == nil { + // check if user tries to authenticate by a prior authentication key + // attempt to migrate JWT to new key + userID := token.PrivateClaims()["id"].(string) + var user uuid.UUID + user, err = uuid.Parse(userID) + + if err == nil { + var ok bool + if ok, err = a.database.IsUserAvailableForKeyMigration(user); ok { + // prepare new JWT + tokenString, _ := a.Sign(map[string]interface{}{"id": user}) + cookie := http.Cookie{Name: "jwt", Value: tokenString, Path: "/", HttpOnly: true, MaxAge: math.MaxInt32} + common.SealCookie(r, &cookie) + http.SetCookie(w, &cookie) + + // update rotation flag in database for user, ignore errors + _, _ = a.database.SetKeyMigration(user) + } else { + err = errors.New("not permitted to access key rotation") + } + } + } else { + // attempt to verify request by new key + token, err = jwtauth.VerifyRequest(a.auth, r, jwtauth.TokenFromCookie) + } + + ctx = jwtauth.NewContext(ctx, token, err) + next.ServeHTTP(w, r.WithContext(ctx)) + } + return http.HandlerFunc(hfn) + } + } + return jwtauth.Verifier(a.auth) +} + +func (a *AuthConfiguration) Authenticator() func(http.Handler) http.Handler { + return jwtauth.Authenticator(a.auth) } func (a *AuthConfiguration) Exists(accountType types.AccountType) bool { - if _, ok := a.providers[string(accountType)]; ok { - return true - } - return false + if _, ok := a.providers[string(accountType)]; ok { + return true + } + return false +} + +func (a *AuthConfiguration) ExtractUserInformation(accountType types.AccountType, user *goth.User) (*UserInformation, error) { + ident := user.UserID + name := user.NickName + avatar := user.AvatarURL + + if ident == "" { + return nil, fmt.Errorf("unable to extract identifier information for user") + } + + if name == "" { + name = user.Name + } + + if name == "" { + return nil, fmt.Errorf("unable to extract name information for user %q", ident) + } + + result := &UserInformation{ + Provider: accountType, + Ident: ident, + Name: name, + AvatarURL: avatar, + } + + return result, nil } -func (a *AuthConfiguration) initializeJWTAuth() { - if a.privateKey == "" { - logger.Get().Warnw("invalid keypair config, falling back to dev keys!") - a.privateKey = devkeys.PrivateKey - } - - if a.unsafePrivateKey != "" { - unsafeKey, err := ssh.ParseRawPrivateKey([]byte(a.unsafePrivateKey)) - if err != nil { - logger.Get().DPanicw("unable to start as we cannot parse unsafe auth keys", "error", err) - } - unsafePrivateKey, ok := unsafeKey.(*ecdsa.PrivateKey) - if !ok { - logger.Get().DPanic("unable to start as the provided unsafe keys are no ecdsa keys") - } - a.unsafeAuth = jwtauth.New("ES512", unsafePrivateKey, unsafePrivateKey.PublicKey) - } - - key, err := ssh.ParseRawPrivateKey([]byte(a.privateKey)) - if err != nil { - logger.Get().DPanicw("unable to start as we cannot parse auth keys", "error", err) - } - privateKey, ok := key.(*ecdsa.PrivateKey) - if !ok { - logger.Get().DPanic("unable to start as the provided keys are no ecdsa keys") - } - - a.auth = jwtauth.New("ES512", privateKey, privateKey.PublicKey) +func (a *AuthConfiguration) initializeJWTAuth() error { + if a.privateKey == "" { + logger.Get().Warnw("invalid keypair config, falling back to dev keys!") + a.privateKey = devkeys.PrivateKey + } + + if a.unsafePrivateKey != "" { + unsafeKey, err := ssh.ParseRawPrivateKey([]byte(a.unsafePrivateKey)) + if err != nil { + return fmt.Errorf("unable parse unsafe auth keys: %w", err) + } + unsafePrivateKey, ok := unsafeKey.(*ecdsa.PrivateKey) + if !ok { + return errors.New("the provided unsafe keys are no ecdsa keys") + } + a.unsafeAuth = jwtauth.New("ES512", unsafePrivateKey, unsafePrivateKey.PublicKey) + } + + key, err := ssh.ParseRawPrivateKey([]byte(a.privateKey)) + if err != nil { + return fmt.Errorf("unable to parse auth keys: %w", err) + } + privateKey, ok := key.(*ecdsa.PrivateKey) + if !ok { + return errors.New("the provided keys are no ecdsa keys") + } + + a.auth = jwtauth.New("ES512", privateKey, privateKey.PublicKey) + return nil } diff --git a/server/src/common/dto/board_templates.go b/server/src/common/dto/board_templates.go index 12e247b16d..59a4de7812 100644 --- a/server/src/common/dto/board_templates.go +++ b/server/src/common/dto/board_templates.go @@ -24,9 +24,6 @@ type BoardTemplate struct { // The favourite status of the template Favourite *bool `json:"favourite"` - - // The template columns - ColumnTemplates []*ColumnTemplate `json:"templateColumns"` } func (bt *BoardTemplate) From(board database.BoardTemplate) *BoardTemplate { @@ -40,6 +37,42 @@ func (bt *BoardTemplate) From(board database.BoardTemplate) *BoardTemplate { return bt } +type BoardTemplateFull struct { + // The board template id + ID uuid.UUID `json:"id"` + + // The board template creator id + Creator uuid.UUID `json:"creator"` + + // The board template name + Name *string `json:"name,omitempty"` + + // Description of the board template + Description *string `json:"description"` + + // The access policy + AccessPolicy types.AccessPolicy `json:"accessPolicy"` + + // The favourite status of the template + Favourite *bool `json:"favourite"` + + // Board templates associated column templates + ColumnTemplates []*ColumnTemplate +} + +func (bt *BoardTemplateFull) From(board database.BoardTemplateFull) *BoardTemplateFull { + bt.ID = board.ID + bt.Creator = board.Creator + bt.Name = board.Name + bt.Description = board.Description + bt.AccessPolicy = board.AccessPolicy + bt.Favourite = board.Favourite + // parse db to dto column templates with dto helper function ColumnTemplates + bt.ColumnTemplates = ColumnTemplates(board.ColumnTemplates) + + return bt +} + // CreateBoardTemplateRequest represents the request to create a new board template. type CreateBoardTemplateRequest struct { // The name of the board template. @@ -58,7 +91,7 @@ type CreateBoardTemplateRequest struct { Favourite *bool `json:"favourite"` // The column templates to create for the board template. - Columns []*ColumnTemplateRequest `json:"columns"` + Columns []*ColumnTemplateRequest `json:"columnTemplates"` } type BoardTemplateUpdateRequest struct { @@ -76,7 +109,4 @@ type BoardTemplateUpdateRequest struct { // The favourite status of the template Favourite *bool `json:"favourite"` - - // The template columns - ColumnTemplates []*ColumnTemplate `json:"templateColumns"` } diff --git a/server/src/common/dto/boards.go b/server/src/common/dto/boards.go index ff8347e6ab..d5241861c2 100644 --- a/server/src/common/dto/boards.go +++ b/server/src/common/dto/boards.go @@ -142,3 +142,33 @@ type BoardOverview struct { CreatedAt time.Time `json:"createdAt"` Participants int `json:"participants"` } + +type ImportBoardRequest struct { + Board *CreateBoardRequest `json:"board"` + Columns []Column `json:"columns"` + Notes []Note `json:"notes"` + Votings []Voting `json:"votings"` +} + +type FullBoard struct { + Board *Board `json:"board"` + BoardSessionRequests []*BoardSessionRequest `json:"requests"` + BoardSessions []*BoardSession `json:"participants"` + Columns []*Column `json:"columns"` + Notes []*Note `json:"notes"` + Reactions []*Reaction `json:"reactions"` + Votings []*Voting `json:"votings"` + Votes []*Vote `json:"votes"` +} + +func (dtoFullBoard *FullBoard) From(dbFullBoard database.FullBoard) *FullBoard { + dtoFullBoard.Board = new(Board).From(dbFullBoard.Board) + dtoFullBoard.BoardSessionRequests = BoardSessionRequests(dbFullBoard.BoardSessionRequests) + dtoFullBoard.BoardSessions = BoardSessions(dbFullBoard.BoardSessions) + dtoFullBoard.Columns = Columns(dbFullBoard.Columns) + dtoFullBoard.Notes = Notes(dbFullBoard.Notes) + dtoFullBoard.Reactions = Reactions(dbFullBoard.Reactions) + dtoFullBoard.Votings = Votings(dbFullBoard.Votings, dbFullBoard.Votes) + dtoFullBoard.Votes = Votes(dbFullBoard.Votes) + return dtoFullBoard +} diff --git a/server/src/common/dto/column_templates.go b/server/src/common/dto/column_templates.go index 2aa05f37c4..2078863259 100644 --- a/server/src/common/dto/column_templates.go +++ b/server/src/common/dto/column_templates.go @@ -10,19 +10,18 @@ import ( // ColumnTemplate is the response for all column template requests. type ColumnTemplate struct { - // The column template id. ID uuid.UUID `json:"id"` // The board template id, that this column template is bound to. BoardTemplate uuid.UUID `json:"board_template"` - // The description of a board template column. - Description string `json:"description"` - // The column template name. Name string `json:"name"` + // The description of a board template column. + Description string `json:"description"` + // The column template color. Color types.Color `json:"color"` @@ -35,8 +34,8 @@ type ColumnTemplate struct { func (ct *ColumnTemplate) From(column database.ColumnTemplate) *ColumnTemplate { ct.ID = column.ID - ct.Name = column.Name ct.BoardTemplate = column.BoardTemplate + ct.Name = column.Name ct.Description = column.Description ct.Color = column.Color ct.Visible = column.Visible @@ -50,7 +49,6 @@ func (*ColumnTemplate) Render(_ http.ResponseWriter, _ *http.Request) error { // ColumnTemplateRequest represents the request to create a new column template. type ColumnTemplateRequest struct { - // The column template name to set. Name string `json:"name"` @@ -71,6 +69,27 @@ type ColumnTemplateRequest struct { User uuid.UUID `json:"-"` } +// ColumnTemplateUpdateRequest represents the request to update a column template. +type ColumnTemplateUpdateRequest struct { + // The column template name to set. + Name string `json:"name"` + + // The columnTemplate description to set. + Description string `json:"description"` + + // The column template color to set. + Color types.Color `json:"color"` + + // Sets whether this column template should be visible to regular participants. + Visible bool `json:"visible"` + + // Sets the index of this column template in the sort order. + Index int `json:"index"` + + ID uuid.UUID `json:"-"` + BoardTemplate uuid.UUID `json:"-"` +} + func ColumnTemplates(columns []database.ColumnTemplate) []*ColumnTemplate { if columns == nil { return nil diff --git a/server/src/common/dto/notes.go b/server/src/common/dto/notes.go index d64a2ed42c..f5c9b3cd0b 100644 --- a/server/src/common/dto/notes.go +++ b/server/src/common/dto/notes.go @@ -77,6 +77,15 @@ type NoteCreateRequest struct { User uuid.UUID `json:"-"` } +type NoteImportRequest struct { + // The text of the note. + Text string `json:"text"` + Position NotePosition `json:"position"` + + Board uuid.UUID `json:"-"` + User uuid.UUID `json:"-"` +} + // NoteUpdateRequest represents the request to update a note. type NoteUpdateRequest struct { diff --git a/server/src/common/dto/vote.go b/server/src/common/dto/vote.go index 6d296ef296..c5e9af63cf 100644 --- a/server/src/common/dto/vote.go +++ b/server/src/common/dto/vote.go @@ -10,11 +10,13 @@ import ( type Vote struct { Voting uuid.UUID `json:"voting"` Note uuid.UUID `json:"note"` + User uuid.UUID `json:"user"` } func (v *Vote) From(vote database.Vote) *Vote { v.Voting = vote.Voting v.Note = vote.Note + v.User = vote.User return v } diff --git a/server/src/common/http.go b/server/src/common/http.go index b9251b0c47..d38489df27 100644 --- a/server/src/common/http.go +++ b/server/src/common/http.go @@ -1,9 +1,9 @@ package common import ( - "log" "net" "net/http" + "scrumlr.io/server/logger" "strings" "github.com/weppos/publicsuffix-go/publicsuffix" @@ -30,7 +30,7 @@ func GetTopLevelHost(r *http.Request) string { hostname := GetHostWithoutPort(r) domain, err := publicsuffix.Domain(hostname) if err != nil { - log.Printf("Error getting domain for %s: %v", hostname, err) + logger.Get().Warnw("Error getting domain", "hostname", hostname, "err", err) return "" } return domain diff --git a/server/src/database/board_templates.go b/server/src/database/board_templates.go index 2156ecbeb6..1b57ce44d8 100644 --- a/server/src/database/board_templates.go +++ b/server/src/database/board_templates.go @@ -2,7 +2,6 @@ package database import ( "context" - "fmt" "time" "github.com/google/uuid" @@ -14,6 +13,17 @@ import ( ) type BoardTemplate struct { + bun.BaseModel `bun:"table:board_templates"` + ID uuid.UUID + Creator uuid.UUID + Name *string + Description *string + AccessPolicy types.AccessPolicy + Favourite *bool + CreatedAt time.Time +} + +type BoardTemplateFull struct { bun.BaseModel `bun:"table:board_templates"` ID uuid.UUID Creator uuid.UUID @@ -21,40 +31,28 @@ type BoardTemplate struct { Description *string AccessPolicy types.AccessPolicy Favourite *bool - CreatedAt time.Time ColumnTemplates []ColumnTemplate + CreatedAt time.Time } -type BoardTemplateGetter struct { +type BoardTemplateInsert struct { bun.BaseModel `bun:"table:board_templates"` - ID uuid.UUID Creator uuid.UUID Name *string Description *string AccessPolicy types.AccessPolicy Favourite *bool - CreatedAt time.Time } -type BoardTemplateInsert struct { +type BoardTemplateUpdate struct { bun.BaseModel `bun:"table:board_templates"` - Creator uuid.UUID + ID uuid.UUID Name *string Description *string - AccessPolicy types.AccessPolicy + AccessPolicy *types.AccessPolicy Favourite *bool } -type BoardTemplateUpdate struct { - bun.BaseModel `bun:"table:board_templates"` - ID uuid.UUID - Name *string - Description *string - AccessPolicy *types.AccessPolicy - Favourite *bool - ColumnTemplates []ColumnTemplate -} - func (d *Database) CreateBoardTemplate(board BoardTemplateInsert, columns []ColumnTemplateInsert) (BoardTemplate, error) { boardInsert := d.db.NewInsert().Model(&board).Returning("*") @@ -66,6 +64,7 @@ func (d *Database) CreateBoardTemplate(board BoardTemplateInsert, columns []Colu columns[index].Index = &newColumnIndex } + // create columns query = query.With("createdColumns", d.db.NewInsert(). Model(&columns). Value("board_template", "(SELECT id FROM \"createdBoardTemplate\")")) @@ -80,7 +79,7 @@ func (d *Database) CreateBoardTemplate(board BoardTemplateInsert, columns []Colu } func (d *Database) GetBoardTemplate(id uuid.UUID) (BoardTemplate, error) { - var tBoard BoardTemplateGetter + var tBoard BoardTemplate // Get settings err := d.db.NewSelect().Model(&tBoard).Where("id = ?", id).Scan(context.Background()) @@ -88,43 +87,26 @@ func (d *Database) GetBoardTemplate(id uuid.UUID) (BoardTemplate, error) { return BoardTemplate{}, err } - // Get columns - var tColumns []ColumnTemplate - err = d.db.NewSelect().Model(&tColumns).Where("board_template = ?", tBoard.ID).Order("index ASC").Scan(context.Background()) - if err != nil { - return BoardTemplate{}, err - } - - dbBoardTemplate := BoardTemplate{ - ID: tBoard.ID, - Creator: tBoard.Creator, - Name: tBoard.Name, - Description: tBoard.Description, - AccessPolicy: tBoard.AccessPolicy, - Favourite: tBoard.Favourite, - ColumnTemplates: tColumns, - } - - return dbBoardTemplate, err + return tBoard, err } -func (d *Database) GetBoardTemplates(user uuid.UUID) ([]BoardTemplate, error) { - var tBoards []BoardTemplateGetter +func (d *Database) GetBoardTemplates(user uuid.UUID) ([]BoardTemplateFull, error) { + var tBoards []BoardTemplate err := d.db.NewSelect().Model(&tBoards).Where("creator = ?", user).Order("created_at ASC").Scan(context.Background()) if err != nil { - return []BoardTemplate{}, err + return []BoardTemplateFull{}, err } - var templates []BoardTemplate + var templates []BoardTemplateFull for _, board := range tBoards { var cols []ColumnTemplate err = d.db.NewSelect().Model(&cols).Where("board_template = ?", board.ID).Scan(context.Background()) if err != nil { - return []BoardTemplate{}, err + return []BoardTemplateFull{}, err } - dbBoardTemplate := BoardTemplate{ + dbBoardTemplate := BoardTemplateFull{ ID: board.ID, Creator: board.Creator, Name: board.Name, @@ -140,95 +122,37 @@ func (d *Database) GetBoardTemplates(user uuid.UUID) ([]BoardTemplate, error) { return templates, err } -func (d *Database) UpdateBoardTemplate(update BoardTemplateUpdate) (BoardTemplate, error) { +func (d *Database) UpdateBoardTemplate(board BoardTemplateUpdate) (BoardTemplate, error) { // General Settings - query_settings := d.db.NewUpdate().Model(&update) + query_settings := d.db.NewUpdate().Model(&board) - if update.Name != nil { + if board.Name != nil { query_settings.Column("name") } - if update.Description != nil { + if board.Description != nil { query_settings.Column("description") } - if update.AccessPolicy != nil { + if board.AccessPolicy != nil { query_settings.Column("access_policy") } - if update.Favourite != nil { + if board.Favourite != nil { query_settings.Column("favourite") } var boardTemplate BoardTemplate _, err := query_settings. - Where("id = ?", update.ID). + Where("id = ?", board.ID). Returning("*"). Exec(common.ContextWithValues(context.Background(), "Database", d, "Result", &boardTemplate), &boardTemplate) if err != nil { - logger.Get().Errorw("failed to update board template settings", "board", update.ID, "err", err) + logger.Get().Errorw("failed to update board template settings", "board", board.ID, "err", err) return BoardTemplate{}, err } - // columns - cols_updated := []ColumnTemplate{} - for _, col := range update.ColumnTemplates { - column := ColumnTemplateUpdate{ - ID: col.ID, - BoardTemplate: boardTemplate.ID, - Name: col.Name, - Description: col.Description, - Color: col.Color, - Visible: col.Visible, - Index: col.Index, - } - - // Update logic - newIndex := column.Index - if column.Index < 0 { - newIndex = 0 - } - - selectPrevious := d.db.NewSelect().Model((*ColumnTemplate)(nil)).Column("board_template", "index").Where("id = ?", column.ID).Where("board_template = ?", column.BoardTemplate) - maxIndexSelect := d.db.NewSelect().Model((*ColumnTemplate)(nil)).Column("index").Where("board_template = ?", column.BoardTemplate) - updateOnSmallerIndex := d.db.NewUpdate(). - Model((*ColumnTemplate)(nil)). - Column("index"). - Set("index = index+1"). - Where("index < (SELECT index FROM \"selectPrevious\")"). - Where("board_template = ?", column.BoardTemplate). - Where("(SELECT index FROM \"selectPrevious\") > ?", newIndex). - Where("index >= ?", newIndex) - updateOnGreaterIndex := d.db.NewUpdate(). - Model((*ColumnTemplate)(nil)). - Column("index"). - Set("index = index-1"). - Where("index > (SELECT index FROM \"selectPrevious\")"). - Where("board_template = ?", column.BoardTemplate). - Where("(SELECT index FROM \"selectPrevious\") < ?", newIndex). - Where("index <= ?", newIndex) - - var c ColumnTemplate - _, err := d.db.NewUpdate(). - With("selectPrevious", selectPrevious). - With("maxIndexSelect", maxIndexSelect). - With("updateOnSmallerIndex", updateOnSmallerIndex). - With("updateOnGreaterIndex", updateOnGreaterIndex). - Model(&column). - Value("index", fmt.Sprintf("LEAST((SELECT COUNT(*) FROM \"maxIndexSelect\")-1, %d)", newIndex)). - Where("id = ?", column.ID). - Returning("*"). - Exec(common.ContextWithValues(context.Background(), "Database", d, identifiers.BoardTemplateIdentifier, column.BaseModel), &c) - - if err != nil { - logger.Get().Errorw("failed to update column template in updte board template", "board_template", update.ID, "err", err) - return BoardTemplate{}, err - } - cols_updated = append(cols_updated, c) - } - boardTemplate.ColumnTemplates = cols_updated - return boardTemplate, err } diff --git a/server/src/database/board_templates_test.go b/server/src/database/board_templates_test.go new file mode 100644 index 0000000000..6d5f4ca99c --- /dev/null +++ b/server/src/database/board_templates_test.go @@ -0,0 +1,426 @@ +package database + +import ( + "database/sql" + "testing" + + "github.com/stretchr/testify/assert" + "scrumlr.io/server/database/types" +) + +func TestRunnerForBoardTemplates(t *testing.T) { + t.Run("Create=0", testCreatePublicBoardTemplate) + t.Run("Create=1", testCreateByPassphraseBoardTemplate) + t.Run("Create=2", testCreateByInviteBoardTemplate) + t.Run("Create=3", testCreateBoardTemplateAlsoCreatesColumnTemplates) + t.Run("Create=4", testCreateBoardTemplateWithName) + t.Run("Create=5", testCreateBoardTemplateWithDescription) + + t.Run("Update=0", testUpdatePublicBoardTemplateToPassphraseBoardTemplate) + t.Run("Update=1", testUpdatePublicBoardTemplateToByInviteBoardTemplate) + t.Run("Update=2", testUpdateByPassphraseBoardTemplateToByInviteBoardTemplate) + t.Run("Update=3", testUpdateByInviteBoardTemplateToByPassphraseBoardTemplate) + t.Run("Update=4", testUpdateBoardTemplateName) + t.Run("Update=5", testUpdateBoardTemplateDescription) + t.Run("Update=6", testUpdateBoardTemplateFavouriteToTrue) + t.Run("Update=7", testUpdateBoardTemplateFavouriteFromTrueToFalse) + + t.Run("Get=0", testGetBoardTemplate) + t.Run("Get=1", testGetAllBoardTemplatesForSpecificUser) + + t.Run("Delete=0", testDeleteBoardTemplate) +} + +func testCreatePublicBoardTemplate(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: nil, + Description: nil, + Favourite: nil, + AccessPolicy: types.AccessPolicyPublic, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template.ID) + assert.Nil(t, template.Name) + assert.Nil(t, template.Description) + assert.False(t, *template.Favourite) + assert.Equal(t, types.AccessPolicyPublic, template.AccessPolicy) +} + +func testCreateByPassphraseBoardTemplate(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: nil, + Description: nil, + Favourite: nil, + AccessPolicy: types.AccessPolicyPublic, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template.ID) + assert.Nil(t, template.Name) + assert.Nil(t, template.Description) + assert.False(t, *template.Favourite) + assert.Equal(t, types.AccessPolicyPublic, template.AccessPolicy) +} + +func testCreateByInviteBoardTemplate(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: nil, + Description: nil, + Favourite: nil, + AccessPolicy: types.AccessPolicyPublic, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template.ID) + assert.Nil(t, template.Name) + assert.Nil(t, template.Description) + assert.False(t, *template.Favourite) + assert.Equal(t, types.AccessPolicyPublic, template.AccessPolicy) +} + +func testCreateBoardTemplateAlsoCreatesColumnTemplates(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + visible := true + notVisible := false + indexOne := 0 + indexTwo := 1 + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: nil, + Description: nil, + Favourite: nil, + AccessPolicy: types.AccessPolicyPublic, + }, []ColumnTemplateInsert{ + { + Name: "A", + Description: "A description", + Color: "backlog-blue", + Visible: &visible, + Index: &indexOne, + }, + { + Name: "B", + Description: "B description", + Color: "backlog-blue", + Visible: ¬Visible, + Index: &indexTwo, + }, + }) + + assert.Nil(t, err) + assert.NotNil(t, template) + + columns, err := testDb.ListColumnTemplates(template.ID) + assert.Nil(t, err) + assert.NotNil(t, columns) + + // Col One + assert.Equal(t, "A", columns[0].Name) + assert.Equal(t, "A description", columns[0].Description) + assert.True(t, columns[0].Visible) + assert.Equal(t, indexOne, columns[0].Index) + + // Col Two + assert.Equal(t, "B", columns[1].Name) + assert.Equal(t, "B description", columns[1].Description) + assert.False(t, columns[1].Visible) + assert.Equal(t, indexTwo, columns[1].Index) +} + +func testCreateBoardTemplateWithName(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + name := "Test Template" + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: &name, + Description: nil, + Favourite: nil, + AccessPolicy: types.AccessPolicyPublic, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template.ID) + assert.Equal(t, "Test Template", *template.Name) +} + +func testCreateBoardTemplateWithDescription(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + description := "Test Description" + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: nil, + Description: &description, + Favourite: nil, + AccessPolicy: types.AccessPolicyPublic, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template.ID) + assert.Equal(t, "Test Description", *template.Description) +} + +func testUpdatePublicBoardTemplateToPassphraseBoardTemplate(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: nil, + AccessPolicy: types.AccessPolicyPublic, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template) + + updateAccessPolicy := types.AccessPolicyByPassphrase + updatedBoard, err := testDb.UpdateBoardTemplate(BoardTemplateUpdate{ + ID: template.ID, + AccessPolicy: &updateAccessPolicy, + }) + + assert.Nil(t, err) + assert.Equal(t, updateAccessPolicy, updatedBoard.AccessPolicy) +} + +func testUpdatePublicBoardTemplateToByInviteBoardTemplate(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: nil, + AccessPolicy: types.AccessPolicyPublic, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template) + + updateAccessPolicy := types.AccessPolicyByInvite + updatedBoard, err := testDb.UpdateBoardTemplate(BoardTemplateUpdate{ + ID: template.ID, + AccessPolicy: &updateAccessPolicy, + }) + + assert.Nil(t, err) + assert.Equal(t, updateAccessPolicy, updatedBoard.AccessPolicy) +} + +func testUpdateByPassphraseBoardTemplateToByInviteBoardTemplate(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: nil, + AccessPolicy: types.AccessPolicyByPassphrase, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template) + + updateAccessPolicy := types.AccessPolicyByInvite + updatedBoard, err := testDb.UpdateBoardTemplate(BoardTemplateUpdate{ + ID: template.ID, + AccessPolicy: &updateAccessPolicy, + }) + + assert.Nil(t, err) + assert.Equal(t, updateAccessPolicy, updatedBoard.AccessPolicy) +} + +func testUpdateByInviteBoardTemplateToByPassphraseBoardTemplate(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: nil, + AccessPolicy: types.AccessPolicyByInvite, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template) + + updateAccessPolicy := types.AccessPolicyByPassphrase + updatedBoard, err := testDb.UpdateBoardTemplate(BoardTemplateUpdate{ + ID: template.ID, + AccessPolicy: &updateAccessPolicy, + }) + + assert.Nil(t, err) + assert.Equal(t, updateAccessPolicy, updatedBoard.AccessPolicy) +} + +func testUpdateBoardTemplateName(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: nil, + AccessPolicy: types.AccessPolicyByInvite, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template) + + updateName := "New Name" + updatedBoard, err := testDb.UpdateBoardTemplate(BoardTemplateUpdate{ + ID: template.ID, + Name: &updateName, + }) + + assert.Nil(t, err) + assert.Equal(t, updateName, *updatedBoard.Name) +} + +func testUpdateBoardTemplateDescription(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Description: nil, + AccessPolicy: types.AccessPolicyByInvite, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template) + + updateDescription := "New Description" + updatedBoard, err := testDb.UpdateBoardTemplate(BoardTemplateUpdate{ + ID: template.ID, + Description: &updateDescription, + }) + + assert.Nil(t, err) + assert.Equal(t, updateDescription, *updatedBoard.Description) +} + +func testUpdateBoardTemplateFavouriteToTrue(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Favourite: nil, + AccessPolicy: types.AccessPolicyByInvite, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template) + + updateFavourite := true + updatedBoard, err := testDb.UpdateBoardTemplate(BoardTemplateUpdate{ + ID: template.ID, + Favourite: &updateFavourite, + }) + + assert.Nil(t, err) + assert.Equal(t, updateFavourite, *updatedBoard.Favourite) +} + +func testUpdateBoardTemplateFavouriteFromTrueToFalse(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + favourited := true + + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Favourite: &favourited, + AccessPolicy: types.AccessPolicyByInvite, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template) + + updateFavourite := false + updatedBoard, err := testDb.UpdateBoardTemplate(BoardTemplateUpdate{ + ID: template.ID, + Favourite: &updateFavourite, + }) + + assert.Nil(t, err) + assert.Equal(t, updateFavourite, *updatedBoard.Favourite) +} + +func testGetBoardTemplate(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + name := "Get Template Test" + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: &name, + AccessPolicy: types.AccessPolicyPublic, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template) + + getTemplate, err := testDb.GetBoardTemplate(template.ID) + + assert.Nil(t, err) + assert.NotNil(t, getTemplate) + assert.IsType(t, BoardTemplate{}, template) + assert.NotNil(t, getTemplate.ID) + assert.Equal(t, name, *template.Name) +} + +func testGetAllBoardTemplatesForSpecificUser(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + name := "Get Template Test" + templateOne, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: &name, + AccessPolicy: types.AccessPolicyPublic, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, templateOne) + + templateTwo, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: &name, + AccessPolicy: types.AccessPolicyPublic, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, templateTwo) + + getTemplates, err := testDb.GetBoardTemplates(user.ID) + + assert.Nil(t, err) + assert.IsType(t, []BoardTemplateFull{}, getTemplates) + assert.NotNil(t, getTemplates) +} + +func testDeleteBoardTemplate(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + name := "Delete Template Test" + template, err := testDb.CreateBoardTemplate(BoardTemplateInsert{ + Creator: user.ID, + Name: &name, + AccessPolicy: types.AccessPolicyPublic, + }, []ColumnTemplateInsert{}) + + assert.Nil(t, err) + assert.NotNil(t, template) + + err = testDb.DeleteBoardTemplate(template.ID) + assert.Nil(t, err) + + _, err = testDb.GetBoard(template.ID) + assert.NotNil(t, err) + assert.Equal(t, err, sql.ErrNoRows) +} diff --git a/server/src/database/boards.go b/server/src/database/boards.go index 38638d7ef6..2669e4919c 100644 --- a/server/src/database/boards.go +++ b/server/src/database/boards.go @@ -1,15 +1,15 @@ package database import ( - "context" - "errors" - "scrumlr.io/server/identifiers" - "time" - - "github.com/google/uuid" - "github.com/uptrace/bun" - "scrumlr.io/server/common" - "scrumlr.io/server/database/types" + "context" + "errors" + "scrumlr.io/server/identifiers" + "time" + + "github.com/google/uuid" + "github.com/uptrace/bun" + "scrumlr.io/server/common" + "scrumlr.io/server/database/types" ) type Board struct { diff --git a/server/src/database/boards_test.go b/server/src/database/boards_test.go index c6932de0b8..9f986b52f2 100644 --- a/server/src/database/boards_test.go +++ b/server/src/database/boards_test.go @@ -17,6 +17,7 @@ func TestRunnerForBoards(t *testing.T) { t.Run("Create=5", testCreateByPassphraseBoard) t.Run("Create=6", testCreateByInviteBoard) t.Run("Create=7", testCreateBoardWithName) + t.Run("Create=8", testCreateBoardWithDescription) t.Run("Update=0", testChangePublicBoardToPassphraseBoard) t.Run("Update=1", testChangeToPassphraseBoardWithMissingPassphraseShouldFail) @@ -29,6 +30,7 @@ func TestRunnerForBoards(t *testing.T) { t.Run("Update=8", testChangeInviteBoardToPublicBoardShouldFail) t.Run("Update=9", testUpdateBoardName) t.Run("Update=10", testUpdateBoardSettings) + t.Run("Update=11", testUpdateBoardDescription) t.Run("Get=0", testGetBoard) t.Run("Get=1", testGetUserBoards) @@ -182,6 +184,22 @@ func testCreateBoardWithName(t *testing.T) { assert.Equal(t, name, *board.Name) } +func testCreateBoardWithDescription(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + description := "A board description" + board, err := testDb.CreateBoard(user.ID, BoardInsert{ + Name: nil, + AccessPolicy: types.AccessPolicyPublic, + Passphrase: nil, + Salt: nil, + Description: &description, + }, []ColumnInsert{}) + + assert.Nil(t, err) + assert.Equal(t, description, *board.Description) +} + func testChangePublicBoardToPassphraseBoard(t *testing.T) { user := fixture.MustRow("User.jack").(*User) @@ -451,6 +469,26 @@ func testUpdateBoardSettings(t *testing.T) { assert.Equal(t, isLocked, updatedBoard.IsLocked) } +func testUpdateBoardDescription(t *testing.T) { + user := fixture.MustRow("User.jack").(*User) + + board, err := testDb.CreateBoard(user.ID, BoardInsert{ + Name: nil, + AccessPolicy: types.AccessPolicyByInvite, + Passphrase: nil, + Salt: nil, + }, []ColumnInsert{}) + + assert.Nil(t, err) + + description := "New description" + + updatedBoard, err := testDb.UpdateBoard(BoardUpdate{ID: board.ID, Description: &description}) + + assert.Nil(t, err) + assert.Equal(t, description, *updatedBoard.Description) +} + func testGetBoard(t *testing.T) { board := fixture.MustRow("Board.boardTestBoard").(*Board) @@ -459,6 +497,7 @@ func testGetBoard(t *testing.T) { assert.Equal(t, board.ID, gotBoard.ID) assert.Equal(t, board.Name, gotBoard.Name) + assert.Equal(t, board.Description, gotBoard.Description) assert.Equal(t, board.ShowAuthors, gotBoard.ShowAuthors) assert.Equal(t, board.ShowNotesOfOtherUsers, gotBoard.ShowNotesOfOtherUsers) assert.Equal(t, board.AccessPolicy, gotBoard.AccessPolicy) diff --git a/server/src/database/column_templates.go b/server/src/database/column_templates.go index d7686ef786..c95aed9a9e 100644 --- a/server/src/database/column_templates.go +++ b/server/src/database/column_templates.go @@ -2,10 +2,14 @@ package database import ( "context" + "fmt" + "math" "github.com/google/uuid" "github.com/uptrace/bun" + "scrumlr.io/server/common" "scrumlr.io/server/database/types" + "scrumlr.io/server/identifiers" ) // ColumnTemplate the model for a column template of a board template @@ -43,9 +47,106 @@ type ColumnTemplateUpdate struct { Index int } -func (d *Database) GetTemplateColumns(tBoard uuid.UUID) ([]ColumnTemplate, error) { +// CreateColumnTemplate creates a new column template. The index will be set to the highest available or the specified one. All other +// indices will be adopted (increased by 1) to the new index. +func (d *Database) CreateColumnTemplate(column ColumnTemplateInsert) (ColumnTemplate, error) { + maxIndexSelect := d.db.NewSelect().Model((*ColumnTemplate)(nil)).ColumnExpr("COUNT(*) as index").Where("board_template = ?", column.BoardTemplate) + + newIndex := math.MaxInt + if column.Index != nil { + if *column.Index < 0 { + newIndex = 0 + } else { + newIndex = *column.Index + } + } + + query := d.db.NewInsert() + if column.Index != nil { + indexUpdate := d.db.NewUpdate().Model((*ColumnTemplate)(nil)).Set("index = index+1").Where("index >= ?", newIndex).Where("board_template = ?", column.BoardTemplate) + query = query.With("indexUpdate", indexUpdate) + } + + var c ColumnTemplate + _, err := query. + With("maxIndexSelect", maxIndexSelect). + Model(&column). + Value("index", fmt.Sprintf("LEAST(coalesce((SELECT index FROM \"maxIndexSelect\"),0), %d)", newIndex)). + Returning("*"). + Exec(common.ContextWithValues(context.Background(), "Database", d, identifiers.BoardTemplateIdentifier, column.BoardTemplate), &c) + + return c, err +} + +// GetColumnTemplate returns the column template for the specified id. +func (d *Database) GetColumnTemplate(board, id uuid.UUID) (ColumnTemplate, error) { + var column ColumnTemplate + err := d.db.NewSelect().Model(&column).Where("board_template = ?", board).Where("id = ?", id).Scan(context.Background()) + return column, err +} + +func (d *Database) ListColumnTemplates(tBoard uuid.UUID) ([]ColumnTemplate, error) { var tColumns []ColumnTemplate err := d.db.NewSelect().Model(&tColumns).Where("board_template = ?", tBoard).Order("index ASC").Scan(context.Background()) return tColumns, err +} + +// UpdateColumnTemplate updates the column template and re-orders all indices of the column templates if necessary. +func (d *Database) UpdateColumnTemplate(column ColumnTemplateUpdate) (ColumnTemplate, error) { + newIndex := column.Index + if column.Index < 0 { + newIndex = 0 + } + + selectPrevious := d.db.NewSelect().Model((*ColumnTemplate)(nil)).Column("board_template", "index").Where("id = ?", column.ID).Where("board_template = ?", column.BoardTemplate) + maxIndexSelect := d.db.NewSelect().Model((*ColumnTemplate)(nil)).Column("index").Where("board_template = ?", column.BoardTemplate) + updateOnSmallerIndex := d.db.NewUpdate(). + Model((*ColumnTemplate)(nil)). + Column("index"). + Set("index = index+1"). + Where("index < (SELECT index FROM \"selectPrevious\")"). + Where("board_template = ?", column.BoardTemplate). + Where("(SELECT index FROM \"selectPrevious\") > ?", newIndex). + Where("index >= ?", newIndex) + updateOnGreaterIndex := d.db.NewUpdate(). + Model((*ColumnTemplate)(nil)). + Column("index"). + Set("index = index-1"). + Where("index > (SELECT index FROM \"selectPrevious\")"). + Where("board_template = ?", column.BoardTemplate). + Where("(SELECT index FROM \"selectPrevious\") < ?", newIndex). + Where("index <= ?", newIndex) + + var c ColumnTemplate + _, err := d.db.NewUpdate(). + With("selectPrevious", selectPrevious). + With("maxIndexSelect", maxIndexSelect). + With("updateOnSmallerIndex", updateOnSmallerIndex). + With("updateOnGreaterIndex", updateOnGreaterIndex). + Model(&column). + Value("index", fmt.Sprintf("LEAST((SELECT COUNT(*) FROM \"maxIndexSelect\")-1, %d)", newIndex)). + Where("id = ?", column.ID). + Returning("*"). + Exec(common.ContextWithValues(context.Background(), "Database", d, identifiers.BoardTemplateIdentifier, column.BoardTemplate), &c) + + return c, err +} + +// DeleteColumnTemplate deletes a column template and adapts all indices of the other columns. +func (d *Database) DeleteColumnTemplate(board, column, user uuid.UUID) error { + var columns []ColumnTemplate + selectPreviousIndex := d.db.NewSelect().Model((*ColumnTemplate)(nil)).Column("index", "board_template").Where("id = ?", column) + indexUpdate := d.db.NewUpdate(). + With("selectPreviousIndex", selectPreviousIndex). + Model((*ColumnTemplate)(nil)).Set("index = index-1"). + Where("board_template = (SELECT board_template from \"selectPreviousIndex\")"). + Where("index >= (SELECT index from \"selectPreviousIndex\")") + _, err := d.db.NewDelete(). + With("indexUpdate", indexUpdate). + Model((*ColumnTemplate)(nil)). + Where("id = ?", column). + Returning("*"). + Exec(common.ContextWithValues(context.Background(), "Database", d, identifiers.BoardTemplateIdentifier, board, identifiers.ColumnTemplateIdentifier, column, identifiers.UserIdentifier, user, "Result", &columns), &columns) + return err } diff --git a/server/src/database/column_templates_test.go b/server/src/database/column_templates_test.go new file mode 100644 index 0000000000..4cc6e1daa9 --- /dev/null +++ b/server/src/database/column_templates_test.go @@ -0,0 +1,346 @@ +package database + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "scrumlr.io/server/database/types" +) + +var boardForColumnTemplatesTest uuid.UUID +var firstColumnTemplate *ColumnTemplate +var secondColumnTemplate *ColumnTemplate +var thirdColumnTemplate *ColumnTemplate +var columnTemplateInsertedFirst *ColumnTemplate +var columnTemplateInsertedSecond *ColumnTemplate +var columnTemplateInsertedThird *ColumnTemplate +var columnTemplateInsertedFourth *ColumnTemplate +var columnTemplateInsertedFifth *ColumnTemplate +var columnTemplateTestUser *User + +func TestRunnerForColumnTemplates(t *testing.T) { + firstColumnTemplate = fixture.MustRow("ColumnTemplate.firstColumnTemplate").(*ColumnTemplate) + secondColumnTemplate = fixture.MustRow("ColumnTemplate.secondColumnTemplate").(*ColumnTemplate) + thirdColumnTemplate = fixture.MustRow("ColumnTemplate.thirdColumnTemplate").(*ColumnTemplate) + columnTemplateTestUser = fixture.MustRow("User.justin").(*User) + boardForColumnTemplatesTest = firstColumnTemplate.BoardTemplate + + t.Run("Getter=0", testGetColumnTemplate) + t.Run("Getter=1", testGetColumnTemplates) + + t.Run("Create=0", testCreateColumnTemplateOnFirstIndex) + t.Run("Create=1", testCreateColumnTemplateOnLastIndex) + t.Run("Create=2", testCreateColumnTemplateOnNegativeIndex) + t.Run("Create=3", testCreateColumnTemplateWithExceptionallyHighIndex) + t.Run("Create=4", testCreateColumnTemplateOnSecondIndex) + t.Run("Create=5", testCreateColumnTemplateWithEmptyName) + t.Run("Create=6", testCreateColumnTemplateWithEmptyColor) + t.Run("Create=7", testCreateColumnTemplateWithDescription) + + t.Run("Delete=0", testDeleteColumnTemplateOnSecondIndex) + t.Run("Delete=1", testDeleteColumnTemplateOnFirstIndex) + t.Run("Delete=2", testDeleteLastColumnTemplate) + t.Run("Delete=3", testDeleteOtherColumnTemplates) + + t.Run("Update=0", testUpdateColumnTemplateName) + t.Run("Update=1", testUpdateColumnTemplateColor) + t.Run("Update=2", testUpdateColumnTemplateVisibility) + t.Run("Update=3", testMoveFirstColumnTemplateOnLastIndex) + t.Run("Update=4", testMoveLastColumnTemplateOnFirstIndex) + t.Run("Update=5", testMoveFirstColumnTemplateOnSecondIndex) + t.Run("Update=6", testMoveSecondColumnTemplateOnFirstIndex) + t.Run("Update=7", testUpdateColumnTemplateDescription) +} + +func testGetColumnTemplate(t *testing.T) { + column := fixture.MustRow("ColumnTemplate.firstColumnTemplate").(*ColumnTemplate) + gotColumn, err := testDb.GetColumnTemplate(boardForColumnTemplatesTest, column.ID) + assert.Nil(t, err) + assert.Equal(t, column.ID, gotColumn.ID) + assert.Equal(t, column.BoardTemplate, gotColumn.BoardTemplate) + assert.Equal(t, column.Name, gotColumn.Name) + assert.Equal(t, column.Description, gotColumn.Description) + assert.Equal(t, column.Visible, gotColumn.Visible) + assert.Equal(t, column.Index, gotColumn.Index) +} + +func testGetColumnTemplates(t *testing.T) { + verifyColumnTemplateOrder(t, firstColumnTemplate.ID, secondColumnTemplate.ID, thirdColumnTemplate.ID) +} + +func testCreateColumnTemplateOnFirstIndex(t *testing.T) { + visible := true + index := 0 + + column, err := testDb.CreateColumnTemplate(ColumnTemplateInsert{ + BoardTemplate: boardForColumnTemplatesTest, + Name: "0 Column", + Color: types.ColorBacklogBlue, + Visible: &visible, + Index: &index, + }) + assert.Nil(t, err) + assert.Equal(t, index, column.Index) + + verifyColumnTemplateOrder(t, column.ID, firstColumnTemplate.ID, secondColumnTemplate.ID, thirdColumnTemplate.ID) + columnTemplateInsertedFirst = &column +} + +func testCreateColumnTemplateOnLastIndex(t *testing.T) { + visible := true + index := 4 + + column, err := testDb.CreateColumnTemplate(ColumnTemplateInsert{ + BoardTemplate: boardForColumnTemplatesTest, + Name: "4 Column", + Description: "Test description", + Color: types.ColorBacklogBlue, + Visible: &visible, + Index: &index, + }) + + assert.Nil(t, err) + assert.Equal(t, index, column.Index) + + verifyColumnTemplateOrder(t, columnTemplateInsertedFirst.ID, firstColumnTemplate.ID, secondColumnTemplate.ID, thirdColumnTemplate.ID, column.ID) + columnTemplateInsertedSecond = &column +} + +func testCreateColumnTemplateOnNegativeIndex(t *testing.T) { + visible := true + index := -99 + expectedIndex := 0 + + column, err := testDb.CreateColumnTemplate(ColumnTemplateInsert{ + BoardTemplate: boardForColumnTemplatesTest, + Name: "-99 Column", + Color: types.ColorBacklogBlue, + Visible: &visible, + Index: &index, + }) + assert.Nil(t, err) + assert.Equal(t, expectedIndex, column.Index) + + verifyColumnTemplateOrder(t, column.ID, columnTemplateInsertedFirst.ID, firstColumnTemplate.ID, secondColumnTemplate.ID, thirdColumnTemplate.ID, columnTemplateInsertedSecond.ID) + columnTemplateInsertedThird = &column +} + +func testCreateColumnTemplateWithExceptionallyHighIndex(t *testing.T) { + visible := true + index := 99 + expectedIndex := 6 + + column, err := testDb.CreateColumnTemplate(ColumnTemplateInsert{ + BoardTemplate: boardForColumnTemplatesTest, + Name: "99 Column", + Color: types.ColorBacklogBlue, + Visible: &visible, + Index: &index, + }) + assert.Nil(t, err) + assert.Equal(t, expectedIndex, column.Index) + + verifyColumnTemplateOrder(t, columnTemplateInsertedThird.ID, columnTemplateInsertedFirst.ID, firstColumnTemplate.ID, secondColumnTemplate.ID, thirdColumnTemplate.ID, columnTemplateInsertedSecond.ID, column.ID) + columnTemplateInsertedFourth = &column +} + +func testCreateColumnTemplateOnSecondIndex(t *testing.T) { + visible := true + index := 1 + + column, err := testDb.CreateColumnTemplate(ColumnTemplateInsert{ + BoardTemplate: boardForColumnTemplatesTest, + Name: "1 Column", + Color: types.ColorBacklogBlue, + Visible: &visible, + Index: &index, + }) + assert.Nil(t, err) + assert.Equal(t, index, column.Index) + + verifyColumnTemplateOrder(t, columnTemplateInsertedThird.ID, column.ID, columnTemplateInsertedFirst.ID, firstColumnTemplate.ID, secondColumnTemplate.ID, thirdColumnTemplate.ID, columnTemplateInsertedSecond.ID, columnTemplateInsertedFourth.ID) + columnTemplateInsertedFifth = &column +} + +func testCreateColumnTemplateWithEmptyName(t *testing.T) { + index := 99 + _, err := testDb.CreateColumnTemplate(ColumnTemplateInsert{ + BoardTemplate: boardForColumnTemplatesTest, + Name: "", + Color: types.ColorBacklogBlue, + Index: &index, + }) + assert.NotNil(t, err) +} + +func testCreateColumnTemplateWithEmptyColor(t *testing.T) { + _, err := testDb.CreateColumnTemplate(ColumnTemplateInsert{ + BoardTemplate: boardForColumnTemplatesTest, + Name: "Column", + Color: "", + }) + assert.NotNil(t, err) +} + +func testCreateColumnTemplateWithDescription(t *testing.T) { + aDescription := "A column template description" + column, err := testDb.CreateColumnTemplate(ColumnTemplateInsert{ + BoardTemplate: boardForColumnTemplatesTest, + Name: "Column", + Color: types.ColorBacklogBlue, + Description: aDescription, + }) + assert.Nil(t, err) + assert.NotNil(t, column) + assert.Equal(t, aDescription, column.Description) + + // clean up to not crash other tests + _ = testDb.DeleteColumnTemplate(boardForColumnTemplatesTest, column.ID, uuid.New()) +} + +func testDeleteColumnTemplateOnSecondIndex(t *testing.T) { + err := testDb.DeleteColumnTemplate(boardForColumnTemplatesTest, columnTemplateInsertedFifth.ID, columnTemplateTestUser.ID) + assert.Nil(t, err) + + verifyColumnTemplateOrder(t, columnTemplateInsertedThird.ID, columnTemplateInsertedFirst.ID, firstColumnTemplate.ID, secondColumnTemplate.ID, thirdColumnTemplate.ID, columnTemplateInsertedSecond.ID, columnTemplateInsertedFourth.ID) +} + +func testDeleteColumnTemplateOnFirstIndex(t *testing.T) { + err := testDb.DeleteColumnTemplate(boardForColumnTemplatesTest, columnTemplateInsertedThird.ID, columnTemplateTestUser.ID) + assert.Nil(t, err) + + verifyColumnTemplateOrder(t, columnTemplateInsertedFirst.ID, firstColumnTemplate.ID, secondColumnTemplate.ID, thirdColumnTemplate.ID, columnTemplateInsertedSecond.ID, columnTemplateInsertedFourth.ID) +} + +func testDeleteLastColumnTemplate(t *testing.T) { + err := testDb.DeleteColumnTemplate(boardForColumnTemplatesTest, columnTemplateInsertedFourth.ID, columnTemplateTestUser.ID) + assert.Nil(t, err) + + verifyColumnTemplateOrder(t, columnTemplateInsertedFirst.ID, firstColumnTemplate.ID, secondColumnTemplate.ID, thirdColumnTemplate.ID, columnTemplateInsertedSecond.ID) +} + +func testDeleteOtherColumnTemplates(t *testing.T) { + _ = testDb.DeleteColumnTemplate(boardForColumnTemplatesTest, columnTemplateInsertedFirst.ID, columnTemplateTestUser.ID) + _ = testDb.DeleteColumnTemplate(boardForColumnTemplatesTest, columnTemplateInsertedSecond.ID, columnTemplateTestUser.ID) + + verifyColumnTemplateOrder(t, firstColumnTemplate.ID, secondColumnTemplate.ID, thirdColumnTemplate.ID) +} + +func testUpdateColumnTemplateName(t *testing.T) { + column, err := testDb.UpdateColumnTemplate(ColumnTemplateUpdate{ + ID: firstColumnTemplate.ID, + BoardTemplate: boardForColumnTemplatesTest, + Name: "Updated name", + Color: types.ColorBacklogBlue, + Visible: false, + Index: 0, + }) + assert.Nil(t, err) + assert.Equal(t, "Updated name", column.Name) +} + +func testUpdateColumnTemplateColor(t *testing.T) { + column, err := testDb.UpdateColumnTemplate(ColumnTemplateUpdate{ + ID: firstColumnTemplate.ID, + BoardTemplate: boardForColumnTemplatesTest, + Name: "Updated name", + Color: types.ColorPlanningPink, + Visible: false, + Index: 0, + }) + assert.Nil(t, err) + assert.Equal(t, types.ColorPlanningPink, column.Color) +} + +func testUpdateColumnTemplateVisibility(t *testing.T) { + column, err := testDb.UpdateColumnTemplate(ColumnTemplateUpdate{ + ID: firstColumnTemplate.ID, + BoardTemplate: boardForColumnTemplatesTest, + Name: "First column", + Color: types.ColorBacklogBlue, + Visible: true, + Index: 0, + }) + assert.Nil(t, err) + assert.Equal(t, true, column.Visible) +} + +func testMoveFirstColumnTemplateOnLastIndex(t *testing.T) { + _, err := testDb.UpdateColumnTemplate(ColumnTemplateUpdate{ + ID: firstColumnTemplate.ID, + BoardTemplate: boardForColumnTemplatesTest, + Name: "First column", + Color: types.ColorBacklogBlue, + Visible: true, + Index: 100, + }) + assert.Nil(t, err) + verifyColumnTemplateOrder(t, secondColumnTemplate.ID, thirdColumnTemplate.ID, firstColumnTemplate.ID) +} + +func testMoveLastColumnTemplateOnFirstIndex(t *testing.T) { + _, err := testDb.UpdateColumnTemplate(ColumnTemplateUpdate{ + ID: firstColumnTemplate.ID, + BoardTemplate: boardForColumnTemplatesTest, + Name: "First column", + Color: types.ColorBacklogBlue, + Visible: true, + Index: 0, + }) + assert.Nil(t, err) + verifyColumnTemplateOrder(t, firstColumnTemplate.ID, secondColumnTemplate.ID, thirdColumnTemplate.ID) +} + +func testMoveFirstColumnTemplateOnSecondIndex(t *testing.T) { + _, err := testDb.UpdateColumnTemplate(ColumnTemplateUpdate{ + ID: firstColumnTemplate.ID, + BoardTemplate: boardForColumnTemplatesTest, + Name: "First column", + Color: types.ColorBacklogBlue, + Visible: true, + Index: 1, + }) + assert.Nil(t, err) + verifyColumnTemplateOrder(t, secondColumnTemplate.ID, firstColumnTemplate.ID, thirdColumnTemplate.ID) +} + +func testMoveSecondColumnTemplateOnFirstIndex(t *testing.T) { + _, err := testDb.UpdateColumnTemplate(ColumnTemplateUpdate{ + ID: firstColumnTemplate.ID, + BoardTemplate: boardForColumnTemplatesTest, + Name: "First column", + Color: types.ColorBacklogBlue, + Visible: true, + Index: 0, + }) + assert.Nil(t, err) + verifyColumnTemplateOrder(t, firstColumnTemplate.ID, secondColumnTemplate.ID, thirdColumnTemplate.ID) +} + +func testUpdateColumnTemplateDescription(t *testing.T) { + column, err := testDb.UpdateColumnTemplate(ColumnTemplateUpdate{ + ID: firstColumnTemplate.ID, + BoardTemplate: boardForColumnTemplatesTest, + Name: "FirstColumn", + Description: "Updated Column Template Description", + Color: types.ColorBacklogBlue, + Visible: true, + Index: 0, + }) + assert.Nil(t, err) + assert.Equal(t, "Updated Column Template Description", column.Description) +} + +func verifyColumnTemplateOrder(t *testing.T, ids ...uuid.UUID) { + expectedOrder := ids + + columns, err := testDb.ListColumnTemplates(boardForColumnTemplatesTest) + assert.Nil(t, err) + assert.Equal(t, len(ids), len(columns)) + + for index, value := range columns { + assert.Equal(t, expectedOrder[index], value.ID) + assert.Equal(t, index, value.Index) + } +} diff --git a/server/src/database/database.go b/server/src/database/database.go index ae1abb5ffb..cf41cdd2ed 100644 --- a/server/src/database/database.go +++ b/server/src/database/database.go @@ -15,6 +15,17 @@ type Database struct { db *bun.DB } +type FullBoard struct { + Board Board + BoardSessions []BoardSession + BoardSessionRequests []BoardSessionRequest + Columns []Column + Notes []Note + Reactions []Reaction + Votings []Voting + Votes []Vote +} + // New creates a new instance of Database func New(db *sql.DB, verbose bool) *Database { d := new(Database) @@ -32,51 +43,52 @@ func New(db *sql.DB, verbose bool) *Database { return d } -func (d *Database) Get(id uuid.UUID) (Board, []BoardSessionRequest, []BoardSession, []Column, []Note, []Reaction, []Voting, []Vote, error) { - var board Board - var sessions []BoardSession - var requests []BoardSessionRequest - var columns []Column - var notes []Note - var reactions []Reaction - var votings []Voting - var votes []Vote - var err error - - board, err = d.GetBoard(id) - if err != nil { - return Board{}, nil, nil, nil, nil, nil, nil, nil, err - } - - requests, err = d.GetBoardSessionRequests(id) - if err != nil { - return Board{}, nil, nil, nil, nil, nil, nil, nil, err - } - - sessions, err = d.GetBoardSessions(id) - if err != nil { - return Board{}, nil, nil, nil, nil, nil, nil, nil, err - } - - columns, err = d.GetColumns(id) - if err != nil { - return Board{}, nil, nil, nil, nil, nil, nil, nil, err - } - - notes, err = d.GetNotes(id) - if err != nil { - return Board{}, nil, nil, nil, nil, nil, nil, nil, err - } - - reactions, err = d.GetReactions(id) - if err != nil { - return Board{}, nil, nil, nil, nil, nil, nil, nil, err - } - - votings, votes, err = d.GetVotings(id) - if err != nil { - return Board{}, nil, nil, nil, nil, nil, nil, nil, err +func (d *Database) Get(id uuid.UUID) (FullBoard, error) { + var ( + board Board + sessions []BoardSession + requests []BoardSessionRequest + columns []Column + notes []Note + reactions []Reaction + votings []Voting + votes []Vote + err error + ) + type dataBaseOperation int + + /* The following const can be compared to an enum in Java + iota allows for an automatic increment in Go */ + const ( + getBoard dataBaseOperation = iota + getRequests + getSessions + getColumns + getNotes + getReactions + getVotings + ) + + for op := getBoard; op <= getVotings; op++ { + switch op { + case getBoard: + board, err = d.GetBoard(id) + case getRequests: + requests, err = d.GetBoardSessionRequests(id) + case getSessions: + sessions, err = d.GetBoardSessions(id) + case getColumns: + columns, err = d.GetColumns(id) + case getNotes: + notes, err = d.GetNotes(id) + case getReactions: + reactions, err = d.GetReactions(id) + case getVotings: + votings, votes, err = d.GetVotings(id) + } + if err != nil { + return FullBoard{}, err + } } - - return board, requests, sessions, columns, notes, reactions, votings, votes, err + return FullBoard{board, sessions, requests, columns, notes, reactions, votings, votes}, nil } diff --git a/server/src/database/database_test.go b/server/src/database/database_test.go index 4e92c38e1c..5747aa17a6 100644 --- a/server/src/database/database_test.go +++ b/server/src/database/database_test.go @@ -66,7 +66,7 @@ func initDatabase() (string, func(), error) { // pulls an image, creates a container based on it and runs it resource, err := pool.RunWithOptions(&dockertest.RunOptions{ Repository: "postgres", - Tag: "16.3", + Tag: "16.4", Env: []string{ fmt.Sprintf("POSTGRES_PASSWORD=%s", DatabaseUsernameAndPassword), fmt.Sprintf("POSTGRES_USER=%s", DatabaseUsernameAndPassword), @@ -118,6 +118,8 @@ func loadTestdata() error { (*Voting)(nil), (*Vote)(nil), (*Reaction)(nil), + (*BoardTemplate)(nil), + (*ColumnTemplate)(nil), ) fixture = dbfixture.New(testDb.db) return fixture.Load(context.Background(), os.DirFS("testdata"), "fixture.yml") diff --git a/server/src/database/migrations/sql/20_add_oidc_auth.down.sql b/server/src/database/migrations/sql/20_add_oidc_auth.down.sql new file mode 100644 index 0000000000..f0e8fb9825 --- /dev/null +++ b/server/src/database/migrations/sql/20_add_oidc_auth.down.sql @@ -0,0 +1 @@ +drop table if exists oidc_users; diff --git a/server/src/database/migrations/sql/20_add_oidc_auth.up.sql b/server/src/database/migrations/sql/20_add_oidc_auth.up.sql new file mode 100644 index 0000000000..fecd531436 --- /dev/null +++ b/server/src/database/migrations/sql/20_add_oidc_auth.up.sql @@ -0,0 +1,9 @@ +alter type account_type add value 'OIDC'; + +create table oidc_users +( + "user" uuid not null references users ON DELETE CASCADE, + id varchar(64) not null unique, + name varchar(64) not null, + avatar_url varchar(256) +); diff --git a/server/src/database/notes.go b/server/src/database/notes.go index ba97598448..b9ee51cc59 100644 --- a/server/src/database/notes.go +++ b/server/src/database/notes.go @@ -33,6 +33,14 @@ type NoteInsert struct { Text string } +type NoteImport struct { + bun.BaseModel `bun:"table:notes"` + Author uuid.UUID + Board uuid.UUID + Text string + Position *NoteUpdatePosition `bun:",embed"` +} + type NoteUpdatePosition struct { Column uuid.UUID Rank int @@ -58,6 +66,16 @@ func (d *Database) CreateNote(insert NoteInsert) (Note, error) { return note, err } +func (d *Database) ImportNote(insert NoteImport) (Note, error) { + var note Note + query := d.db.NewInsert(). + Model(&insert). + Returning("*") + _, err := query.Exec(common.ContextWithValues(context.Background(), "Database", d, identifiers.BoardIdentifier, insert.Board), ¬e) + + return note, err +} + func (d *Database) GetNote(id uuid.UUID) (Note, error) { var note Note err := d.db.NewSelect().Model((*Note)(nil)).Where("id = ?", id).Scan(context.Background(), ¬e) @@ -74,6 +92,15 @@ func (d *Database) GetNotes(board uuid.UUID, columns ...uuid.UUID) ([]Note, erro return notes, err } +func (d *Database) GetChildNotes(parentNote uuid.UUID) ([]Note, error) { + var notes []Note + err := d.db.NewSelect().Model((*Note)(nil)).Where("stack = ?", parentNote).Scan(context.Background(), ¬es) + if err != nil { + return nil, err + } + return notes, nil +} + func (d *Database) UpdateNote(caller uuid.UUID, update NoteUpdate) (Note, error) { boardSelect := d.db.NewSelect().Model((*Board)(nil)).Column("allow_stacking").Where("id = ?", update.Board) sessionSelect := d.db.NewSelect().Model((*BoardSession)(nil)).Column("role").Where("\"user\" = ?", caller).Where("board = ?", update.Board) diff --git a/server/src/database/testdata/fixture.yml b/server/src/database/testdata/fixture.yml index beaa0defa3..aebf88f7ea 100644 --- a/server/src/database/testdata/fixture.yml +++ b/server/src/database/testdata/fixture.yml @@ -31,6 +31,17 @@ account_type: ANONYMOUS created_at: '{{ now }}' +- model: BoardTemplate + rows: + - _id: columnTemplatesTestBoard + id: "3113b096-986c-4e23-adf7-b3fa19224bd4" + creator: "62e0ea41-6fbf-49a3-8921-1d2f4e5ae316" + name: John's template board + description: Board template test + access_policy: PUBLIC + favourite: false + created_at: '{{ now }}' + - model: Board rows: - _id: columnsTestBoard @@ -57,6 +68,7 @@ - _id: boardTestBoard id: "5113b096-986c-4e23-adf7-c3fa19224bd4" name: Board test + description: Description test access_policy: PUBLIC show_authors: true show_notes_of_other_users: true @@ -103,48 +115,6 @@ show_authors: true show_notes_of_other_users: true created_at: '{{ now }}' - - _id: columnsObserverTestBoard - id: "2813b096-986c-0000-0000-c3fa19224bd4" - name: Columns observer test - access_policy: PUBLIC - show_authors: true - show_notes_of_other_users: true - created_at: '{{ now }}' - - _id: boardsObserverTestBoard - id: "2813b096-986c-0000-0001-c3fa19224bd4" - name: Boards observer test - access_policy: PUBLIC - show_authors: true - show_notes_of_other_users: true - created_at: '{{ now }}' - - _id: boardSessionsObserverTestBoard - id: "2813b096-986c-0000-0002-c3fa19224bd4" - name: Board sessions observer test - access_policy: PUBLIC - show_authors: true - show_notes_of_other_users: true - created_at: '{{ now }}' - - _id: boardSessionRequestsObserverTestBoard - id: "2813b096-986c-0000-0003-c3fa19224bd4" - name: Board sessions observer test - access_policy: PUBLIC - show_authors: true - show_notes_of_other_users: true - created_at: '{{ now }}' - - _id: notesObserverTestBoard - id: "2813b096-986c-0000-0004-c3fa19224bd4" - name: Notes sessions observer test - access_policy: PUBLIC - show_authors: true - show_notes_of_other_users: true - created_at: '{{ now }}' - - _id: votingObserverTestBoard - id: "2813b096-986c-0000-0005-c3fa19224bd4" - name: Voting observer test - access_policy: PUBLIC - show_authors: true - show_notes_of_other_users: true - created_at: '{{ now }}' - _id: votingSortingTestBoard id: "1acd6899-ad71-4479-8ffe-6c6401b208d7" name: Voting Sorting test @@ -202,14 +172,6 @@ board: '{{ $.Board.notesTestBoard.ID }}' user: '{{ $.User.jack.ID }}' role: OWNER - - _id: jacksSessionOnNotesObserverTestBoard - board: '{{ $.Board.notesObserverTestBoard.ID }}' - user: '{{ $.User.jack.ID }}' - role: OWNER - - _id: jaysSessionOnBoardSessionsObserverTestBoard - board: '{{ $.Board.boardSessionsObserverTestBoard.ID }}' - user: '{{ $.User.jay.ID }}' - role: OWNER - _id: justinSessionOnVotingSortingTestBoard board: '{{ $.Board.votingSortingTestBoard.ID }}' user: '{{ $.User.justin.ID }}' @@ -219,6 +181,33 @@ user: '{{ $.User.justin.ID }}' role: OWNER +- model: ColumnTemplate + rows: + - _id: firstColumnTemplate + id: "2813b000-0000-4e23-adf7-c3fa19224bd4" + board_template: '{{ $.BoardTemplate.columnTemplatesTestBoard.ID }}' + name: First column + description: Fist column description + color: backlog-blue + visible: true + index: 0 + - _id: secondColumnTemplate + id: "2813b000-0001-4e23-adf7-c3fa19224bd4" + board_template: '{{ $.BoardTemplate.columnTemplatesTestBoard.ID }}' + name: Second column + description: Second column description + color: backlog-blue + visible: true + index: 1 + - _id: thirdColumnTemplate + id: "2813b000-0002-4e23-adf7-c3fa19224bd4" + board_template: '{{ $.BoardTemplate.columnTemplatesTestBoard.ID }}' + name: Third column + description: Third column description + color: backlog-blue + visible: true + index: 2 + - model: Column rows: - _id: firstColumn @@ -298,13 +287,6 @@ color: backlog-blue visible: true index: 0 - - _id: notesObserverTestColumn - id: "3113b196-986c-2951-adf7-c3fa19224bd4" - board: '{{ $.Board.notesObserverTestBoard.ID }}' - name: B - color: backlog-blue - visible: true - index: 0 - _id: votingSortingColumn id: "00c8c51b-d391-4472-8136-06efdfe7bbd2" board: '{{ $.Board.votingSortingTestBoard.ID }}' diff --git a/server/src/database/types/account_type.go b/server/src/database/types/account_type.go index 97cc06e1b5..bccfd2870d 100644 --- a/server/src/database/types/account_type.go +++ b/server/src/database/types/account_type.go @@ -3,6 +3,7 @@ package types import ( "encoding/json" "errors" + "strings" ) // AccountType of users (e.g. the authentication provider) @@ -26,18 +27,33 @@ const ( // AccountTypeApple users registered on Apple AccountTypeApple AccountType = "APPLE" + + // AccountTypeOIDC users registered on OIDC + AccountTypeOIDC AccountType = "OIDC" ) +func NewAccountType(s string) (result AccountType, err error) { + result = AccountType(strings.ToUpper(s)) + switch result { + case AccountTypeAnonymous, AccountTypeGoogle, AccountTypeMicrosoft, AccountTypeAzureAd, AccountTypeGitHub, AccountTypeApple, AccountTypeOIDC: + return + } + err = errors.New("invalid account type") + + return +} + func (accountType *AccountType) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } - unmarshalledAccountType := AccountType(s) - switch unmarshalledAccountType { - case AccountTypeAnonymous, AccountTypeGoogle, AccountTypeMicrosoft, AccountTypeAzureAd, AccountTypeGitHub, AccountTypeApple: - *accountType = unmarshalledAccountType - return nil + unmarshalledAccountType, err := NewAccountType(s) + if err != nil { + return err } - return errors.New("invalid account type") + + *accountType = unmarshalledAccountType + + return nil } diff --git a/server/src/database/types/account_type_test.go b/server/src/database/types/account_type_test.go index 35f01ec968..2148640aea 100644 --- a/server/src/database/types/account_type_test.go +++ b/server/src/database/types/account_type_test.go @@ -6,8 +6,94 @@ import ( "testing" ) +func TestNewAccountType(t *testing.T) { + tests := map[string]struct { + have string + want AccountType + wantError bool + }{ + "ANONYMOUS": { + have: "ANONYMOUS", + want: AccountTypeAnonymous, + }, + "GOOGLE": { + have: "GOOGLE", + want: AccountTypeGoogle, + }, + "MICROSOFT": { + have: "MICROSOFT", + want: AccountTypeMicrosoft, + }, + "AZURE_AD": { + have: "AZURE_AD", + want: AccountTypeAzureAd, + }, + "GITHUB": { + have: "GITHUB", + want: AccountTypeGitHub, + }, + "APPLE": { + have: "APPLE", + want: AccountTypeApple, + }, + "OIDC": { + have: "OIDC", + want: AccountTypeOIDC, + }, + "ANONYMOUS (lowercase)": { + have: "anonymous", + want: AccountTypeAnonymous, + }, + "GOOGLE (lowercase)": { + have: "google", + want: AccountTypeGoogle, + }, + "MICROSOFT (lowercase)": { + have: "microsoft", + want: AccountTypeMicrosoft, + }, + "AZURE_AD (lowercase)": { + have: "azure_ad", + want: AccountTypeAzureAd, + }, + "GITHUB (lowercase)": { + have: "github", + want: AccountTypeGitHub, + }, + "APPLE (lowercase)": { + have: "apple", + want: AccountTypeApple, + }, + "OIDC (lowercase)": { + have: "oidc", + want: AccountTypeOIDC, + }, + "invalid enum value": { + have: "FACEBOOK", + wantError: true, + }, + } + + for name, test := range tests { + test := test + t.Run(name, func(t *testing.T) { + t.Parallel() + got, gotErr := NewAccountType(test.have) + if test.wantError { + if gotErr == nil { + t.Fatalf("NewAccountType(%q) did not yield an error", test.have) + } + } else { + if got != test.want { + t.Fatalf("NewAccountType(%q) returned %q; wanted %q", test.have, got, test.want) + } + } + }) + } +} + func TestAccountTypeEnum(t *testing.T) { - values := []AccountType{AccountTypeAnonymous, AccountTypeGoogle, AccountTypeGitHub, AccountTypeMicrosoft, AccountTypeApple} + values := []AccountType{AccountTypeAnonymous, AccountTypeGoogle, AccountTypeGitHub, AccountTypeMicrosoft, AccountTypeApple, AccountTypeOIDC} for _, value := range values { var accountType AccountType err := accountType.UnmarshalJSON([]byte(fmt.Sprintf("\"%s\"", value))) diff --git a/server/src/database/types/avatar/accessories_type.go b/server/src/database/types/avatar/accessories_type.go index 7de59810a3..0097efaa2f 100644 --- a/server/src/database/types/avatar/accessories_type.go +++ b/server/src/database/types/avatar/accessories_type.go @@ -2,8 +2,7 @@ package avatar import ( "encoding/json" - - "github.com/pkg/errors" + "errors" ) type AccessoriesType string diff --git a/server/src/database/types/avatar/clothe_color.go b/server/src/database/types/avatar/clothe_color.go index bc275ac41d..2a72861544 100644 --- a/server/src/database/types/avatar/clothe_color.go +++ b/server/src/database/types/avatar/clothe_color.go @@ -3,7 +3,7 @@ package avatar import ( "encoding/json" - "github.com/pkg/errors" + "errors" ) type ClotheColor string diff --git a/server/src/database/types/avatar/clothe_type.go b/server/src/database/types/avatar/clothe_type.go index 698159ecf2..5ff9201985 100644 --- a/server/src/database/types/avatar/clothe_type.go +++ b/server/src/database/types/avatar/clothe_type.go @@ -3,7 +3,7 @@ package avatar import ( "encoding/json" - "github.com/pkg/errors" + "errors" ) type ClotheType string diff --git a/server/src/database/types/avatar/eye_type.go b/server/src/database/types/avatar/eye_type.go index 0fda5adbb4..5caf98a687 100644 --- a/server/src/database/types/avatar/eye_type.go +++ b/server/src/database/types/avatar/eye_type.go @@ -3,7 +3,7 @@ package avatar import ( "encoding/json" - "github.com/pkg/errors" + "errors" ) type EyeType string diff --git a/server/src/database/types/avatar/eyebrow_type.go b/server/src/database/types/avatar/eyebrow_type.go index abe93d25fe..711c8f4884 100644 --- a/server/src/database/types/avatar/eyebrow_type.go +++ b/server/src/database/types/avatar/eyebrow_type.go @@ -3,7 +3,7 @@ package avatar import ( "encoding/json" - "github.com/pkg/errors" + "errors" ) type EyebrowType string diff --git a/server/src/database/types/avatar/facial_hair_color.go b/server/src/database/types/avatar/facial_hair_color.go index 96217e1fe9..16a0ad4395 100644 --- a/server/src/database/types/avatar/facial_hair_color.go +++ b/server/src/database/types/avatar/facial_hair_color.go @@ -3,7 +3,7 @@ package avatar import ( "encoding/json" - "github.com/pkg/errors" + "errors" ) type FacialHairColor string diff --git a/server/src/database/types/avatar/facial_hair_type.go b/server/src/database/types/avatar/facial_hair_type.go index f911cf59ba..5234a20835 100644 --- a/server/src/database/types/avatar/facial_hair_type.go +++ b/server/src/database/types/avatar/facial_hair_type.go @@ -3,7 +3,7 @@ package avatar import ( "encoding/json" - "github.com/pkg/errors" + "errors" ) type FacialHairType string diff --git a/server/src/database/types/avatar/graphic_type.go b/server/src/database/types/avatar/graphic_type.go index 7d790cc253..deeba3d137 100644 --- a/server/src/database/types/avatar/graphic_type.go +++ b/server/src/database/types/avatar/graphic_type.go @@ -3,7 +3,7 @@ package avatar import ( "encoding/json" - "github.com/pkg/errors" + "errors" ) type GraphicType string diff --git a/server/src/database/types/avatar/hair_color.go b/server/src/database/types/avatar/hair_color.go index b639b58052..41ab4577ad 100644 --- a/server/src/database/types/avatar/hair_color.go +++ b/server/src/database/types/avatar/hair_color.go @@ -2,8 +2,7 @@ package avatar import ( "encoding/json" - - "github.com/pkg/errors" + "errors" ) type HairColor string diff --git a/server/src/database/types/avatar/mouth_type.go b/server/src/database/types/avatar/mouth_type.go index 5ce312b9bb..e29a1dc54e 100644 --- a/server/src/database/types/avatar/mouth_type.go +++ b/server/src/database/types/avatar/mouth_type.go @@ -3,7 +3,7 @@ package avatar import ( "encoding/json" - "github.com/pkg/errors" + "errors" ) type MouthType string diff --git a/server/src/database/types/avatar/skin_color.go b/server/src/database/types/avatar/skin_color.go index cbc2b6caa2..7c3ea491f9 100644 --- a/server/src/database/types/avatar/skin_color.go +++ b/server/src/database/types/avatar/skin_color.go @@ -3,7 +3,7 @@ package avatar import ( "encoding/json" - "github.com/pkg/errors" + "errors" ) type SkinColor string diff --git a/server/src/database/types/avatar/top_type.go b/server/src/database/types/avatar/top_type.go index 3d001b158e..e25220a17e 100644 --- a/server/src/database/types/avatar/top_type.go +++ b/server/src/database/types/avatar/top_type.go @@ -3,7 +3,7 @@ package avatar import ( "encoding/json" - "github.com/pkg/errors" + "errors" ) type TopType string diff --git a/server/src/database/users.go b/server/src/database/users.go index fb548dd3ea..6724de19ac 100644 --- a/server/src/database/users.go +++ b/server/src/database/users.go @@ -69,6 +69,10 @@ func (d *Database) CreateAppleUser(id, name, avatarUrl string) (User, error) { return d.createExternalUser(id, name, avatarUrl, types.AccountTypeApple, "apple_users") } +func (d *Database) CreateOIDCUser(id, name, avatarUrl string) (User, error) { + return d.createExternalUser(id, name, avatarUrl, types.AccountTypeOIDC, "oidc_users") +} + func (d *Database) createExternalUser(id, name, avatarUrl string, accountType types.AccountType, table string) (User, error) { if err := validateUsername(name); err != nil { return User{}, err diff --git a/server/src/database/votings.go b/server/src/database/votings.go index c6431647a7..abc8bbe524 100644 --- a/server/src/database/votings.go +++ b/server/src/database/votings.go @@ -152,3 +152,9 @@ func (d *Database) GetVotings(board uuid.UUID) ([]Voting, []Vote, error) { votes, err := d.GetVotes(filter.VoteFilter{Board: board}) return votings, votes, err } + +func (d *Database) GetOpenVoting(board uuid.UUID) (Voting, error) { + var voting Voting + err := d.db.NewSelect().Model(&voting).Where("board = ?", board).Where("status = ?", "OPEN").Scan(context.Background()) + return voting, err +} diff --git a/server/src/go.mod b/server/src/go.mod index 3e60c3a9f7..5844d025c9 100644 --- a/server/src/go.mod +++ b/server/src/go.mod @@ -1,91 +1,96 @@ module scrumlr.io/server -go 1.22 +go 1.23 + +toolchain go1.23rc1 // https://github.com/inovex/scrumlr.io/security/dependabot/108 replace github.com/opencontainers/runc v1.1.0 => github.com/opencontainers/runc v1.1.2 require ( - github.com/go-chi/chi/v5 v5.1.0 + github.com/go-chi/chi/v5 v5.2.0 github.com/go-chi/cors v1.2.1 - github.com/go-chi/httprate v0.10.0 - github.com/go-chi/jwtauth/v5 v5.1.1 + github.com/go-chi/httprate v0.14.1 + github.com/go-chi/jwtauth/v5 v5.3.2 github.com/go-chi/render v1.0.3 github.com/go-redis/redis/v8 v8.11.5 - github.com/golang-migrate/migrate/v4 v4.17.1 + github.com/golang-migrate/migrate/v4 v4.18.1 github.com/google/uuid v1.6.0 + github.com/gorilla/sessions v1.4.0 github.com/gorilla/websocket v1.5.3 - github.com/lestrrat-go/jwx/v2 v2.1.0 + github.com/lestrrat-go/jwx/v2 v2.1.3 github.com/markbates/goth v1.80.0 - github.com/nats-io/nats.go v1.36.0 - github.com/ory/dockertest/v3 v3.10.0 - github.com/pkg/errors v0.9.1 - github.com/stretchr/testify v1.9.0 - github.com/uptrace/bun v1.1.17 - github.com/uptrace/bun/dbfixture v1.1.17 - github.com/uptrace/bun/dialect/pgdialect v1.1.17 - github.com/uptrace/bun/extra/bundebug v1.1.17 - github.com/urfave/cli/v2 v2.27.2 + github.com/nats-io/nats.go v1.38.0 + github.com/ory/dockertest/v3 v3.11.0 + github.com/stretchr/testify v1.10.0 + github.com/uptrace/bun v1.2.8 + github.com/uptrace/bun/dbfixture v1.2.8 + github.com/uptrace/bun/dialect/pgdialect v1.2.8 + github.com/uptrace/bun/extra/bundebug v1.2.8 + github.com/urfave/cli/v2 v2.27.5 github.com/weppos/publicsuffix-go v0.40.2 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.25.0 + golang.org/x/crypto v0.32.0 ) require ( - cloud.google.com/go/compute v1.23.3 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/BurntSushi/toml v1.3.2 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect + cloud.google.com/go/compute v1.31.1 // indirect + cloud.google.com/go/compute/metadata v0.6.0 // indirect + dario.cat/mergo v1.0.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/BurntSushi/toml v1.4.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/ajg/form v1.5.1 // indirect - github.com/cenkalti/backoff/v4 v4.1.3 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/containerd/continuity v0.3.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/containerd/continuity v0.4.5 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/docker/cli v20.10.17+incompatible // indirect - github.com/docker/docker v24.0.9+incompatible // indirect - github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/cli v27.4.1+incompatible // indirect + github.com/docker/docker v27.4.1+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/fatih/color v1.16.0 // indirect - github.com/goccy/go-json v0.10.3 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/goccy/go-json v0.10.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.4.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/gorilla/mux v1.8.0 // indirect - github.com/gorilla/securecookie v1.1.1 // indirect - github.com/gorilla/sessions v1.2.1 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/imdario/mergo v0.3.12 // indirect github.com/jinzhu/inflection v1.0.0 // indirect - github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/httprc v1.0.5 // indirect + github.com/lestrrat-go/httprc v1.0.6 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/jwx v1.2.29 // indirect + github.com/lestrrat-go/jwx v1.2.30 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/lib/pq v1.10.9 // indirect github.com/markbates/going v1.0.3 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mitchellh/mapstructure v1.4.3 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/nats-io/nkeys v0.4.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/nats-io/nkeys v0.4.9 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.1.12 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/runc v1.2.4 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect - github.com/sirupsen/logrus v1.9.2 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect @@ -93,18 +98,15 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.10.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/oauth2 v0.25.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.36.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/server/src/go.sum b/server/src/go.sum index 424e6bf018..4115e9c7b9 100644 --- a/server/src/go.sum +++ b/server/src/go.sum @@ -1,29 +1,45 @@ -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= +cloud.google.com/go/compute v1.31.1 h1:SObuy8Fs6woazArpXp1fsHCw+ZH4iJ/8dGGTxUhHZQA= +cloud.google.com/go/compute v1.31.1/go.mod h1:hyOponWhXviDptJCJSoEh89XO1cfv616wbwbkde1/+8= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= -github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= +github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= +github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -35,52 +51,68 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnN github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dhui/dktest v0.4.1 h1:/w+IWuDXVymg3IrRJCHHOkMK10m9aNVMOyD0X12YVTg= -github.com/dhui/dktest v0.4.1/go.mod h1:DdOqcUpL7vgyP4GlF3X3w7HbSlz8cEQzwewPveYEQbA= -github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= -github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= -github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/dhui/dktest v0.4.3 h1:wquqUxAFdcUgabAVLvSCOKOlag5cIZuaOjYIBOWdsR0= +github.com/dhui/dktest v0.4.3/go.mod h1:zNK8IwktWzQRm6I/l2Wjp7MakiyaFWv4G1hjmodmMTs= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= +github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI= +github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4= +github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0= +github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-chi/httprate v0.10.0 h1:NnHj/C9pNR1mwDX9biXXYmLhEhVqz+PR3MR3kWdHYYY= -github.com/go-chi/httprate v0.10.0/go.mod h1:IqW+o6o/dkOUqyR9weVur+ob3gpEemmbfSRiBxUwqJU= -github.com/go-chi/jwtauth/v5 v5.1.1 h1:Pjixqu5YkjE9sCLpzE01L0Q4sQzJIPdo7uz9r8ftp/c= -github.com/go-chi/jwtauth/v5 v5.1.1/go.mod h1:CYP1WSbzD4MPuKCr537EM3kfFhSQgpUEtMJFuYJjqWU= +github.com/go-chi/httprate v0.14.1 h1:EKZHYEZ58Cg6hWcYzoZILsv7ppb46Wt4uQ738IRtpZs= +github.com/go-chi/httprate v0.14.1/go.mod h1:TUepLXaz/pCjmCtf/obgOQJ2Sz6rC8fSf5cAt5cnTt0= +github.com/go-chi/jwtauth/v5 v5.3.2 h1:s+ON3ATyyMs3Me0kqyuua6Rwu+2zqIIkL0GCaMarwvs= +github.com/go-chi/jwtauth/v5 v5.3.2/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= +github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= +github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= -github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= -github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y= +github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -89,16 +121,20 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= +github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -107,14 +143,14 @@ github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -125,14 +161,16 @@ github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= -github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= +github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/jwx v1.2.29 h1:QT0utmUJ4/12rmsVQrJ3u55bycPkKqGYuGT4tyRhxSQ= github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtXSNFeZuB8= -github.com/lestrrat-go/jwx/v2 v2.1.0 h1:0zs7Ya6+39qoit7gwAf+cYm1zzgS3fceIdo7RmQ5lkw= -github.com/lestrrat-go/jwx/v2 v2.1.0/go.mod h1:Xpw9QIaUGiIUD1Wx0NcY1sIHwFf8lDuZn/cmxtXYRys= +github.com/lestrrat-go/jwx v1.2.30 h1:VKIFrmjYn0z2J51iLPadqoHIVLzvWNa1kCsTqNDHYPA= +github.com/lestrrat-go/jwx v1.2.30/go.mod h1:vMxrwFhunGZ3qddmfmEm2+uced8MSI6QFWGTKygjSzQ= +github.com/lestrrat-go/jwx/v2 v2.1.3 h1:Ud4lb2QuxRClYAmRleF50KrbKIoM1TddXgBrneT5/Jo= +github.com/lestrrat-go/jwx/v2 v2.1.3/go.mod h1:q6uFgbgZfEmQrfJfrCo90QcQOcXFMfbI/fO0NqRtvZo= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= @@ -147,16 +185,20 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= -github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU= -github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA= +github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw= +github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0= +github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -170,25 +212,31 @@ github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= -github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= -github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= -github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= +github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= +github.com/opencontainers/runc v1.2.4 h1:yWFgLkghp71D76Fa0l349yAl5g4Gse7DPYNlvkQ9Eiw= +github.com/opencontainers/runc v1.2.4/go.mod h1:nSxcWUydXrsBZVYNSkTjoQ/N6rcyTtn+1SD5D4+kRIM= +github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA= +github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= +github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= -github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= -github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -200,20 +248,23 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= -github.com/uptrace/bun v1.1.17 h1:qxBaEIo0hC/8O3O6GrMDKxqyT+mw5/s0Pn/n6xjyGIk= -github.com/uptrace/bun v1.1.17/go.mod h1:hATAzivtTIRsSJR4B8AXR+uABqnQxr3myKDKEf5iQ9U= -github.com/uptrace/bun/dbfixture v1.1.17 h1:/zvsLC582KizT7Ksignl64OXU5ut5x9sIs/fUhW8ssA= -github.com/uptrace/bun/dbfixture v1.1.17/go.mod h1:7vgT1cIq4gkp+xXuLXApaM5EAMGwkImTqpOzRSw1qiE= -github.com/uptrace/bun/dialect/pgdialect v1.1.17 h1:NsvFVHAx1Az6ytlAD/B6ty3cVE6j9Yp82bjqd9R9hOs= -github.com/uptrace/bun/dialect/pgdialect v1.1.17/go.mod h1:fLBDclNc7nKsZLzNjFL6BqSdgJzbj2HdnyOnLoDvAME= -github.com/uptrace/bun/extra/bundebug v1.1.17 h1:LcZ8DzyyGdXAmbUqmnCpBq7TPFegMp59FGy+uzEE21c= -github.com/uptrace/bun/extra/bundebug v1.1.17/go.mod h1:FOwNaBEGGChv3qBVh3pz3TPlUuikZ93qKjd/LJdl91o= -github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= -github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= +github.com/uptrace/bun v1.2.8 h1:HEiLvy9wc7ehU5S02+O6NdV5BLz48lL4REPhTkMX3Dg= +github.com/uptrace/bun v1.2.8/go.mod h1:JBq0uBKsKqNT0Ccce1IAFZY337Wkf08c6F6qlmfOHE8= +github.com/uptrace/bun/dbfixture v1.2.6 h1:jfgkFE+CfwcwCWm1LykzquTl+JC+obGKYvA+cyA+1Bs= +github.com/uptrace/bun/dbfixture v1.2.6/go.mod h1:EYJJ9CN0h4Jb2Fup7PMZOj4o9zZZCjnQmO/SEX0z8mM= +github.com/uptrace/bun/dbfixture v1.2.8 h1:nMZA0e+VfrVTwJbFZeyTS8TTLCqxH7enRwn4ecQL5/M= +github.com/uptrace/bun/dbfixture v1.2.8/go.mod h1:cEnzmrY5RjaNc8OsISfv2mHIxDiYIFSGmHPvZi2mu8s= +github.com/uptrace/bun/dialect/pgdialect v1.2.8 h1:9n3qVh6yc+u7F3lpXzsWrAFJG1yLHUC2thjCCVEDpM8= +github.com/uptrace/bun/dialect/pgdialect v1.2.8/go.mod h1:plksD43MjAlPGYLD9/SzsLUpGH5poXE9IB1+ka/sEzE= +github.com/uptrace/bun/extra/bundebug v1.2.8 h1:Epv0ycLOnoKWPky+rufP2F/PrcSlKkd4tmVIFOdq90A= +github.com/uptrace/bun/extra/bundebug v1.2.8/go.mod h1:ucnmuPw/5ePbNFj2SPmV0lQh3ZvL+3HCrpvRxIYZyWQ= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -227,17 +278,29 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -249,15 +312,15 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -273,11 +336,16 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -286,8 +354,9 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -307,8 +376,9 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -319,8 +389,9 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -332,8 +403,9 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -341,7 +413,6 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -353,8 +424,11 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= +google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -362,11 +436,10 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= -gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/server/src/identifiers/identifiers.go b/server/src/identifiers/identifiers.go index 77999403cb..22b6deff50 100644 --- a/server/src/identifiers/identifiers.go +++ b/server/src/identifiers/identifiers.go @@ -1,14 +1,5 @@ package identifiers -// -//type BoardIdentifier struct{} -//type UserIdentifier struct{} -//type NoteIdentifier struct{} -//type ColumnIdentifier struct{} -//type ReactionIdentifier struct{} -//type VotingIdentifier struct{} -//type BoardEditableIdentifier struct{} - type boardIdentifier string type userIdentifier string type noteIdentifier string @@ -17,14 +8,16 @@ type reactionIdentifier string type votingIdentifier string type boardEditableIdentifier string type boardTemplateIdentifier string +type columnTemplateIdentifier string const ( - BoardIdentifier boardIdentifier = "Board" - UserIdentifier userIdentifier = "User" - NoteIdentifier noteIdentifier = "Note" - ColumnIdentifier columnIdentifier = "Column" - ReactionIdentifier reactionIdentifier = "Reaction" - VotingIdentifier votingIdentifier = "Voting" - BoardEditableIdentifier boardEditableIdentifier = "BoardEditable" - BoardTemplateIdentifier boardTemplateIdentifier = "BoardTemplate" + BoardIdentifier boardIdentifier = "Board" + UserIdentifier userIdentifier = "User" + NoteIdentifier noteIdentifier = "Note" + ColumnIdentifier columnIdentifier = "Column" + ReactionIdentifier reactionIdentifier = "Reaction" + VotingIdentifier votingIdentifier = "Voting" + BoardEditableIdentifier boardEditableIdentifier = "BoardEditable" + BoardTemplateIdentifier boardTemplateIdentifier = "BoardTemplate" + ColumnTemplateIdentifier columnTemplateIdentifier = "ColumnTemplate" ) diff --git a/server/src/logger/logger.go b/server/src/logger/logger.go index 856e459d2c..6618fb5467 100644 --- a/server/src/logger/logger.go +++ b/server/src/logger/logger.go @@ -19,6 +19,7 @@ const ctxRequestLogger ctxLoggerKey = iota func init() { loggerConfig := zap.NewProductionConfig() loggerConfig.Level = zap.NewAtomicLevelAt(zap.InfoLevel) + loggerConfig.EncoderConfig.StacktraceKey = "" //remove stacktrace from logging logger, _ := loggerConfig.Build() _logger = logger.Sugar() } @@ -93,3 +94,23 @@ func ChiZapLogger() func(next http.Handler) http.Handler { return http.HandlerFunc(fn) } } + +//func InitTestLogger(req *http.Request) *http.Request { +// loggerConfig := zap.NewNop() // Use a no-op logger for testing +// logger := loggerConfig.Sugar() +// req = req.WithContext(AddLoggerToContext(req.Context(), logger)) +// return req +//} + +func InitTestLogger(ctx context.Context) context.Context { + loggerConfig := zap.NewNop() // Use a no-op logger for testing + logger := loggerConfig.Sugar() + + return AddLoggerToContext(ctx, logger) +} + +func InitTestLoggerRequest(req *http.Request) *http.Request { + ctx := InitTestLogger(req.Context()) + req = req.WithContext(ctx) + return req +} diff --git a/server/src/main.go b/server/src/main.go index 8d65930e2e..7868799afb 100644 --- a/server/src/main.go +++ b/server/src/main.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "log" "net/http" @@ -10,6 +11,8 @@ import ( "scrumlr.io/server/auth" "scrumlr.io/server/services/health" + "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2/altsrc" "scrumlr.io/server/api" "scrumlr.io/server/database" "scrumlr.io/server/database/migrations" @@ -24,10 +27,6 @@ import ( "scrumlr.io/server/services/reactions" "scrumlr.io/server/services/users" "scrumlr.io/server/services/votings" - - "github.com/pkg/errors" - "github.com/urfave/cli/v2" - "github.com/urfave/cli/v2/altsrc" ) func main() { @@ -110,6 +109,13 @@ func main() { Required: false, Value: false, }), + altsrc.NewBoolFlag(&cli.BoolFlag{ + Name: "auth-enable-experimental-file-system-store", + EnvVars: []string{"SCRUMLR_ENABLE_EXPERIMENTAL_AUTH_FILE_SYSTEM_STORE"}, + Usage: "enables/disables experimental file system store, in order to allow larger session cookie sizes", + Required: false, + Value: false, + }), altsrc.NewStringFlag(&cli.StringFlag{ Name: "auth-callback-host", Aliases: []string{"c"}, @@ -183,6 +189,42 @@ func main() { Usage: "the client `secret` for Apple", Required: false, }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: "auth-oidc-client-id", + EnvVars: []string{"SCRUMLR_AUTH_OIDC_CLIENT_ID"}, + Usage: "the client `id` for OpenID Connect", + Required: false, + }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: "auth-oidc-client-secret", + EnvVars: []string{"SCRUMLR_AUTH_OIDC_CLIENT_SECRET"}, + Usage: "the client `secret` for OpenID Connect", + Required: false, + }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: "auth-oidc-discovery-url", + EnvVars: []string{"SCRUMLR_AUTH_OIDC_DISCOVERY_URL"}, + Usage: "URL hosting the OIDC discovery document", + Required: false, + }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: "auth-oidc-user-ident-scope", + EnvVars: []string{"SCRUMLR_AUTH_OIDC_USER_IDENT_SCOPE"}, + Usage: "JWT claim to request for the user identifier", + Value: "openid", + }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: "auth-oidc-user-name-scope", + EnvVars: []string{"SCRUMLR_AUTH_OIDC_USER_NAME_SCOPE"}, + Usage: "JWT claim to request for the user name", + Value: "profile", + }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: "session-secret", + EnvVars: []string{"SESSION_SECRET"}, + Usage: "Session secret for the authentication provider. Must be provided if an authentication provider is used.", + Required: false, + }), altsrc.NewBoolFlag(&cli.BoolFlag{ Name: "verbose", Aliases: []string{"v"}, @@ -210,20 +252,19 @@ func main() { } app.Before = altsrc.InitInputSourceWithContext(app.Flags, altsrc.NewTomlSourceFromFlagFunc("config")) - // check if process is executed within docker environment - if _, err := os.Stat("/.dockerenv"); err != nil { - logger.EnableDevelopmentLogger() - } - if err := app.Run(os.Args); err != nil { log.Fatal(err) } } func run(c *cli.Context) error { + if c.Bool("verbose") { + logger.EnableDevelopmentLogger() + } + db, err := migrations.MigrateDatabase(c.String("database")) if err != nil { - return errors.Wrap(err, "unable to migrate database") + return fmt.Errorf("unable to migrate database: %w", err) } if !c.Bool("insecure") && c.String("key") == "" { @@ -232,6 +273,7 @@ func run(c *cli.Context) error { var rt *realtime.Broker if c.String("redis-address") != "" { + logger.Get().Infof("Connecting to redis at %v", c.String("redis-address")) rt, err = realtime.NewRedis(realtime.RedisServer{ Addr: c.String("redis-address"), Username: c.String("redis-username"), @@ -241,6 +283,7 @@ func run(c *cli.Context) error { logger.Get().Fatalf("failed to connect to redis message queue: %v", err) } } else { + logger.Get().Infof("Connecting to nats at %v", c.String("nats")) rt, err = realtime.NewNats(c.String("nats")) if err != nil { logger.Get().Fatalf("failed to connect to nats message queue: %v", err) @@ -260,28 +303,32 @@ func run(c *cli.Context) error { } providersMap := make(map[string]auth.AuthProviderConfiguration) - if c.IsSet("auth-google-client-id") && c.IsSet("auth-google-client-secret") && c.IsSet("auth-callback-host") { + if c.String("auth-google-client-id") != "" && c.String("auth-google-client-secret") != "" && c.String("auth-callback-host") != "" { + logger.Get().Info("Using google authentication") providersMap[(string)(types.AccountTypeGoogle)] = auth.AuthProviderConfiguration{ ClientId: c.String("auth-google-client-id"), ClientSecret: c.String("auth-google-client-secret"), RedirectUri: fmt.Sprintf("%s%s/login/google/callback", strings.TrimSuffix(c.String("auth-callback-host"), "/"), strings.TrimSuffix(basePath, "/")), } } - if c.IsSet("auth-github-client-id") && c.IsSet("auth-github-client-secret") && c.IsSet("auth-callback-host") { + if c.String("auth-github-client-id") != "" && c.String("auth-github-client-secret") != "" && c.String("auth-callback-host") != "" { + logger.Get().Info("Using github authentication") providersMap[(string)(types.AccountTypeGitHub)] = auth.AuthProviderConfiguration{ ClientId: c.String("auth-github-client-id"), ClientSecret: c.String("auth-github-client-secret"), RedirectUri: fmt.Sprintf("%s%s/login/github/callback", strings.TrimSuffix(c.String("auth-callback-host"), "/"), strings.TrimSuffix(basePath, "/")), } } - if c.IsSet("auth-microsoft-client-id") && c.IsSet("auth-microsoft-client-secret") && c.IsSet("auth-callback-host") { + if c.String("auth-microsoft-client-id") != "" && c.String("auth-microsoft-client-secret") != "" && c.String("auth-callback-host") != "" { + logger.Get().Info("Using microsoft authentication") providersMap[(string)(types.AccountTypeMicrosoft)] = auth.AuthProviderConfiguration{ ClientId: c.String("auth-microsoft-client-id"), ClientSecret: c.String("auth-microsoft-client-secret"), RedirectUri: fmt.Sprintf("%s%s/login/microsoft/callback", strings.TrimSuffix(c.String("auth-callback-host"), "/"), strings.TrimSuffix(basePath, "/")), } } - if c.IsSet("auth-azure-ad-tenant-id") && c.IsSet("auth-azure-ad-client-id") && c.IsSet("auth-azure-ad-client-secret") && c.IsSet("auth-callback-host") { + if c.String("auth-azure-ad-tenant-id") != "" && c.String("auth-azure-ad-client-id") != "" && c.String("auth-azure-ad-client-secret") != "" && c.String("auth-callback-host") != "" { + logger.Get().Info("Using azure authentication") providersMap[(string)(types.AccountTypeAzureAd)] = auth.AuthProviderConfiguration{ TenantId: c.String("auth-azure-ad-tenant-id"), ClientId: c.String("auth-azure-ad-client-id"), @@ -289,19 +336,38 @@ func run(c *cli.Context) error { RedirectUri: fmt.Sprintf("%s%s/login/azure_ad/callback", strings.TrimSuffix(c.String("auth-callback-host"), "/"), strings.TrimSuffix(basePath, "/")), } } - if c.IsSet("auth-apple-client-id") && c.IsSet("auth-apple-client-secret") && c.IsSet("auth-callback-host") { + if c.String("auth-apple-client-id") != "" && c.String("auth-apple-client-secret") != "" && c.String("auth-callback-host") != "" { + logger.Get().Info("Using apple authentication.") providersMap[(string)(types.AccountTypeApple)] = auth.AuthProviderConfiguration{ ClientId: c.String("auth-apple-client-id"), ClientSecret: c.String("auth-apple-client-secret"), RedirectUri: fmt.Sprintf("%s%s/login/apple/callback", strings.TrimSuffix(c.String("auth-callback-host"), "/"), strings.TrimSuffix(basePath, "/")), } } + if c.String("auth-oidc-discovery-url") != "" && c.String("auth-oidc-client-id") != "" && c.String("auth-oidc-client-secret") != "" && c.String("auth-callback-host") != "" { + logger.Get().Info("Using oicd authentication.") + providersMap[(string)(types.AccountTypeOIDC)] = auth.AuthProviderConfiguration{ + ClientId: c.String("auth-oidc-client-id"), + ClientSecret: c.String("auth-oidc-client-secret"), + RedirectUri: fmt.Sprintf("%s%s/login/oidc/callback", strings.TrimSuffix(c.String("auth-callback-host"), "/"), strings.TrimSuffix(basePath, "/")), + DiscoveryUri: c.String("auth-oidc-discovery-url"), + UserIdentScope: c.String("auth-oidc-user-ident-scope"), + UserNameScope: c.String("auth-oidc-user-name-scope"), + } + } + + if c.String("session-secret") == "" && len(providersMap) != 0 { + return errors.New("you may not start the application without a session secret if an authentication provider is configured") + } dbConnection := database.New(db, c.Bool("verbose")) keyWithNewlines := strings.ReplaceAll(c.String("key"), "\\n", "\n") unsafeKeyWithNewlines := strings.ReplaceAll(c.String("unsafe-key"), "\\n", "\n") - authConfig := auth.NewAuthConfiguration(providersMap, unsafeKeyWithNewlines, keyWithNewlines, dbConnection) + authConfig, err := auth.NewAuthConfiguration(providersMap, unsafeKeyWithNewlines, keyWithNewlines, dbConnection) + if err != nil { + return fmt.Errorf("unable to setup authentication: %w", err) + } boardService := boards.NewBoardService(dbConnection, rt) boardSessionService := boards.NewBoardSessionService(dbConnection, rt) @@ -333,6 +399,7 @@ func run(c *cli.Context) error { c.Bool("verbose"), !c.Bool("disable-check-origin"), c.Bool("disable-anonymous-login"), + c.Bool("auth-enable-experimental-file-system-store"), ) port := fmt.Sprintf(":%d", c.Int("port")) diff --git a/server/src/realtime/boards.go b/server/src/realtime/boards.go index b24b7b5a37..65ff6fdc8c 100644 --- a/server/src/realtime/boards.go +++ b/server/src/realtime/boards.go @@ -22,7 +22,7 @@ const ( BoardEventReactionAdded BoardEventType = "REACTION_ADDED" BoardEventReactionDeleted BoardEventType = "REACTION_DELETED" BoardEventReactionUpdated BoardEventType = "REACTION_UPDATED" - BoardEventVotesUpdated BoardEventType = "VOTES_UPDATED" + BoardEventVotesDeleted BoardEventType = "VOTES_DELETED" BoardEventSessionRequestCreated BoardEventType = "REQUEST_CREATED" BoardEventSessionRequestUpdated BoardEventType = "REQUEST_UPDATED" BoardEventParticipantCreated BoardEventType = "PARTICIPANT_CREATED" diff --git a/server/src/realtime/nats.go b/server/src/realtime/nats.go index bd3eb8d550..8e1a73c835 100644 --- a/server/src/realtime/nats.go +++ b/server/src/realtime/nats.go @@ -1,54 +1,70 @@ package realtime import ( + "encoding/json" "fmt" "github.com/nats-io/nats.go" + "scrumlr.io/server/logger" ) type natsClient struct { - con *nats.EncodedConn + con *nats.Conn } // Publish the given event to the given subject func (n *natsClient) Publish(subject string, event interface{}) error { - return n.con.Publish(subject, event) + data, err := json.Marshal(event) + if err != nil { + logger.Get().Errorw("unable to marshal event in publish", "subject", subject, "event", event, "err", err) + } + return n.con.Publish(subject, data) } // SubscribeToBoardSessionEvents subscribes to the given subject func (n *natsClient) SubscribeToBoardSessionEvents(subject string) (chan *BoardSessionRequestEventType, error) { receiverChan := make(chan *BoardSessionRequestEventType) - _, err := n.con.BindRecvChan(subject, receiverChan) + _, err := n.con.Subscribe(subject, func(msg *nats.Msg) { + var event BoardSessionRequestEventType + if err := json.Unmarshal(msg.Data, &event); err != nil { + logger.Get().Errorw("unable to unmarshal board session event in subscribeToBoardSessionEvents", "subject", subject, "err", err) + return + } + receiverChan <- &event + }) if err != nil { - return receiverChan, fmt.Errorf("failed to bind to subject %s: %w", subject, err) + return nil, fmt.Errorf("failed to subscribe to subject %s: %w", subject, err) } + return receiverChan, nil } // SubscribeToBoardEvents subscribes to the given subject func (n *natsClient) SubscribeToBoardEvents(subject string) (chan *BoardEvent, error) { receiverChan := make(chan *BoardEvent) - _, err := n.con.BindRecvChan(subject, receiverChan) + _, err := n.con.Subscribe(subject, func(msg *nats.Msg) { + var event BoardEvent + if err := json.Unmarshal(msg.Data, &event); err != nil { + logger.Get().Errorw("unable to unmarshal board event in subscribeToBoardEvents", "subject", subject, "err", err) + return + } + receiverChan <- &event + }) if err != nil { - return receiverChan, fmt.Errorf("failed to bind to subject %s: %w", subject, err) + return nil, fmt.Errorf("failed to subscribe to subject %s: %w", subject, err) } return receiverChan, nil } // NewNats returns a new NATs backed Broker func NewNats(url string) (*Broker, error) { - // Connect to a server nc, err := nats.Connect(url) if err != nil { return nil, fmt.Errorf("unable to connect to nats server %s: %w", url, err) } - c, err := nats.NewEncodedConn(nc, nats.JSON_ENCODER) - if err != nil { - return nil, fmt.Errorf("unable to open encoded connection: %w", err) - } return &Broker{ - Con: &natsClient{con: c}, + Con: &natsClient{con: nc}, }, nil } diff --git a/server/src/services/board_reactions/board_reactions.go b/server/src/services/board_reactions/board_reactions.go index 3947a79c83..d62ed71dbf 100644 --- a/server/src/services/board_reactions/board_reactions.go +++ b/server/src/services/board_reactions/board_reactions.go @@ -5,7 +5,6 @@ import ( "github.com/google/uuid" "scrumlr.io/server/common/dto" _ "scrumlr.io/server/database" - "scrumlr.io/server/logger" "scrumlr.io/server/realtime" "scrumlr.io/server/services" _ "scrumlr.io/server/services" @@ -42,12 +41,8 @@ func (s *BoardReactionService) Create(_ context.Context, board uuid.UUID, body d // AddedReaction creates a broadcast for all connected boards with the added reaction as payload func (s *BoardReactionService) AddedReaction(board uuid.UUID, reaction dto.BoardReaction) { - err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventBoardReactionAdded, Data: reaction, }) - - if err != nil { - logger.Get().Errorw("unable to broadcast updated reactions", "err", err) - } } diff --git a/server/src/services/board_templates/board_templates.go b/server/src/services/board_templates/board_templates.go index 28337f0e0c..2eaba53003 100644 --- a/server/src/services/board_templates/board_templates.go +++ b/server/src/services/board_templates/board_templates.go @@ -32,7 +32,7 @@ func (s *BoardTemplateService) Create(ctx context.Context, body dto.CreateBoardT Favourite: body.Favourite, } - // map request on column objects to insert into database + // map request column templates to db column template inserts columns := make([]database.ColumnTemplateInsert, 0, len(body.Columns)) for index, value := range body.Columns { var currentIndex = index @@ -42,123 +42,65 @@ func (s *BoardTemplateService) Create(ctx context.Context, body dto.CreateBoardT // create the board template b, err := s.database.CreateBoardTemplate(board, columns) if err != nil { - log.Errorw("unable to create board", "creator", body.Creator, "policy", body.AccessPolicy, "error", err) + log.Errorw("unable to create board template", "creator", body.Creator, "policy", body.AccessPolicy, "err", err) return nil, err } + return new(dto.BoardTemplate).From(b), nil } -func (s *BoardTemplateService) Get(_ context.Context, id uuid.UUID) (*dto.BoardTemplate, error) { +func (s *BoardTemplateService) Get(ctx context.Context, id uuid.UUID) (*dto.BoardTemplate, error) { + log := logger.FromContext(ctx) boardTemplate, err := s.database.GetBoardTemplate(id) if err != nil { + log.Errorw("unable to get board template", "board", id, "err", err) return nil, err } - - // convert database cols to dto cols - cols := boardTemplate.ColumnTemplates - var new_cols []*dto.ColumnTemplate - for _, x := range cols { - new_cols = append(new_cols, new(dto.ColumnTemplate).From(x)) - } - - boardDto := dto.BoardTemplate{ - ID: boardTemplate.ID, - Creator: boardTemplate.Creator, - Name: boardTemplate.Name, - Description: boardTemplate.Description, - AccessPolicy: boardTemplate.AccessPolicy, - Favourite: boardTemplate.Favourite, - ColumnTemplates: new_cols, - } - - return &boardDto, err + return new(dto.BoardTemplate).From(boardTemplate), err } -func (s *BoardTemplateService) List(ctx context.Context, user uuid.UUID) ([]*dto.BoardTemplate, error) { +func (s *BoardTemplateService) List(ctx context.Context, user uuid.UUID) ([]*dto.BoardTemplateFull, error) { + log := logger.FromContext(ctx) templates, err := s.database.GetBoardTemplates(user) if err != nil { - return []*dto.BoardTemplate{}, err + log.Errorw("unable to list board templates", "user", user, "err", err) + return []*dto.BoardTemplateFull{}, err } - var templatesDto []*dto.BoardTemplate - for _, board := range templates { - // convert database cols to dto cols - cols := board.ColumnTemplates - var new_cols []*dto.ColumnTemplate - for _, x := range cols { - new_cols = append(new_cols, new(dto.ColumnTemplate).From(x)) - } - - boardDto := dto.BoardTemplate{ - ID: board.ID, - Creator: board.Creator, - Name: board.Name, - Description: board.Description, - AccessPolicy: board.AccessPolicy, - Favourite: board.Favourite, - ColumnTemplates: new_cols, - } - templatesDto = append(templatesDto, &boardDto) + var templatesDto []*dto.BoardTemplateFull + for _, template := range templates { + templatesDto = append(templatesDto, new(dto.BoardTemplateFull).From(template)) } return templatesDto, err } func (s *BoardTemplateService) Update(ctx context.Context, body dto.BoardTemplateUpdateRequest) (*dto.BoardTemplate, error) { - // parse dto cols to db cols - req_cols := []database.ColumnTemplate{} - for _, col := range body.ColumnTemplates { - new_col := database.ColumnTemplate{ - ID: col.ID, - BoardTemplate: col.BoardTemplate, - Name: col.Name, - Description: col.Description, - Color: col.Color, - Visible: col.Visible, - Index: col.Index, - } - req_cols = append(req_cols, new_col) - } - + log := logger.FromContext(ctx) // parse req update to db update - update := database.BoardTemplateUpdate{ - ID: body.ID, - Name: body.Name, - Description: body.Description, - AccessPolicy: body.AccessPolicy, - Favourite: body.Favourite, - ColumnTemplates: req_cols, + updateBoard := database.BoardTemplateUpdate{ + ID: body.ID, + Name: body.Name, + Description: body.Description, + AccessPolicy: body.AccessPolicy, + Favourite: body.Favourite, } - updatedTemplate, err := s.database.UpdateBoardTemplate(update) + updatedTemplate, err := s.database.UpdateBoardTemplate(updateBoard) if err != nil { + log.Errorw("unable to update board template", "board", body.ID, "err", err) return &dto.BoardTemplate{}, err } - // convert database cols to dto cols - cols := updatedTemplate.ColumnTemplates - var new_cols []*dto.ColumnTemplate - for _, x := range cols { - new_cols = append(new_cols, new(dto.ColumnTemplate).From(x)) - } - - boardDto := dto.BoardTemplate{ - ID: updatedTemplate.ID, - Creator: updatedTemplate.Creator, - Name: updatedTemplate.Name, - Description: updatedTemplate.Description, - AccessPolicy: updatedTemplate.AccessPolicy, - Favourite: updatedTemplate.Favourite, - ColumnTemplates: new_cols, - } - - return &boardDto, err + return new(dto.BoardTemplate).From(updatedTemplate), err } func (s *BoardTemplateService) Delete(ctx context.Context, templateId uuid.UUID) error { + log := logger.FromContext(ctx) err := s.database.DeleteBoardTemplate(templateId) if err != nil { - logger.Get().Errorw("unable to delete board template", "err", err) + log.Errorw("unable to delete board template", "board", templateId, "err", err) + return err } return err } diff --git a/server/src/services/board_templates/column_templates.go b/server/src/services/board_templates/column_templates.go index 2999c919ab..1b0d4d81e7 100644 --- a/server/src/services/board_templates/column_templates.go +++ b/server/src/services/board_templates/column_templates.go @@ -6,15 +6,56 @@ import ( "github.com/google/uuid" "scrumlr.io/server/common/dto" + "scrumlr.io/server/database" "scrumlr.io/server/logger" ) -func (s *BoardTemplateService) ListTemplateColumns(ctx context.Context, boardID uuid.UUID) ([]*dto.ColumnTemplate, error) { +func (s *BoardTemplateService) CreateColumnTemplate(ctx context.Context, body dto.ColumnTemplateRequest) (*dto.ColumnTemplate, error) { log := logger.FromContext(ctx) - tColumns, err := s.database.GetTemplateColumns(boardID) + tColumn, err := s.database.CreateColumnTemplate(database.ColumnTemplateInsert{BoardTemplate: body.BoardTemplate, Name: body.Name, Description: body.Description, Color: body.Color, Visible: body.Visible, Index: body.Index}) if err != nil { - log.Errorw("unable to get template columns", "boardemplate", boardID, "error", err) - return nil, fmt.Errorf("unable to get columns: %w", err) + log.Errorw("unable to create column template", "user", body.User, "err", err) + return nil, err + } + return new(dto.ColumnTemplate).From(tColumn), err +} + +func (s *BoardTemplateService) GetColumnTemplate(ctx context.Context, boardTemplate, columnTemplate uuid.UUID) (*dto.ColumnTemplate, error) { + log := logger.FromContext(ctx) + tColumn, err := s.database.GetColumnTemplate(boardTemplate, columnTemplate) + if err != nil { + log.Errorw("unable to get template column", "board", boardTemplate, "err", err) + return nil, fmt.Errorf("unable to get template column: %w", err) + } + return new(dto.ColumnTemplate).From(tColumn), err +} + +func (s *BoardTemplateService) ListColumnTemplates(ctx context.Context, boardTemplate uuid.UUID) ([]*dto.ColumnTemplate, error) { + log := logger.FromContext(ctx) + tColumns, err := s.database.ListColumnTemplates(boardTemplate) + if err != nil { + log.Errorw("unable to get template columns", "board", boardTemplate, "err", err) + return nil, fmt.Errorf("unable to get template columns: %w", err) } return dto.ColumnTemplates(tColumns), err } + +func (s *BoardTemplateService) UpdateColumnTemplate(ctx context.Context, body dto.ColumnTemplateUpdateRequest) (*dto.ColumnTemplate, error) { + log := logger.FromContext(ctx) + tColumn, err := s.database.UpdateColumnTemplate(database.ColumnTemplateUpdate{ID: body.ID, BoardTemplate: body.BoardTemplate, Name: body.Name, Description: body.Description, Color: body.Color, Visible: body.Visible, Index: body.Index}) + if err != nil { + log.Errorw("unable to update column template", "board", body.BoardTemplate, "column", body.ID, "err", err) + return nil, err + } + return new(dto.ColumnTemplate).From(tColumn), err +} + +func (s *BoardTemplateService) DeleteColumnTemplate(ctx context.Context, board, column, user uuid.UUID) error { + log := logger.FromContext(ctx) + err := s.database.DeleteColumnTemplate(board, column, user) + if err != nil { + log.Errorw("unable to delete column template", "board", board, "column", column, "err", err) + return err + } + return err +} diff --git a/server/src/services/boards/boards.go b/server/src/services/boards/boards.go index 99d48d3535..0b45f0b795 100644 --- a/server/src/services/boards/boards.go +++ b/server/src/services/boards/boards.go @@ -6,8 +6,6 @@ import ( "fmt" "time" - "scrumlr.io/server/identifiers" - "github.com/google/uuid" "scrumlr.io/server/common/dto" @@ -32,20 +30,25 @@ func NewBoardService(db *database.Database, rt *realtime.Broker) services.Boards return b } -func (s *BoardService) Get(_ context.Context, id uuid.UUID) (*dto.Board, error) { +func (s *BoardService) Get(ctx context.Context, id uuid.UUID) (*dto.Board, error) { + log := logger.FromContext(ctx) board, err := s.database.GetBoard(id) if err != nil { + log.Errorw("unable to get board", "boardID", id, "err", err) return nil, err } return new(dto.Board).From(board), err } // get all associated boards of a given user -func (s *BoardService) GetBoards(_ context.Context, userID uuid.UUID) ([]uuid.UUID, error) { +func (s *BoardService) GetBoards(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error) { + log := logger.FromContext(ctx) boards, err := s.database.GetBoards(userID) if err != nil { + log.Errorw("unable to get boards of user", "userID", userID, "err", err) return nil, err } + result := make([]uuid.UUID, len(boards)) for i, board := range boards { result[i] = board.ID @@ -92,27 +95,24 @@ func (s *BoardService) Create(ctx context.Context, body dto.CreateBoardRequest) return new(dto.Board).From(b), nil } -func (s *BoardService) FullBoard(ctx context.Context, boardID uuid.UUID) (*dto.Board, []*dto.BoardSessionRequest, []*dto.BoardSession, []*dto.Column, []*dto.Note, []*dto.Reaction, []*dto.Voting, []*dto.Vote, error) { - board, requests, sessions, columns, notes, reactions, votings, votes, err := s.database.Get(boardID) +func (s *BoardService) FullBoard(ctx context.Context, boardID uuid.UUID) (*dto.FullBoard, error) { + log := logger.FromContext(ctx) + fullBoard, err := s.database.Get(boardID) if err != nil { - return nil, nil, nil, nil, nil, nil, nil, nil, err - } - - personalVotes := []*dto.Vote{} - for _, vote := range votes { - if vote.User == ctx.Value(identifiers.UserIdentifier).(uuid.UUID) { - personalVotes = append(personalVotes, new(dto.Vote).From(vote)) - } + log.Errorw("unable to get full board", "boardID", boardID, "err", err) + return nil, err } - return new(dto.Board).From(board), dto.BoardSessionRequests(requests), dto.BoardSessions(sessions), dto.Columns(columns), dto.Notes(notes), dto.Reactions(reactions), dto.Votings(votings, votes), personalVotes, err + return new(dto.FullBoard).From(fullBoard), err } -func (s *BoardService) BoardOverview(_ context.Context, boardIDs []uuid.UUID, user uuid.UUID) ([]*dto.BoardOverview, error) { +func (s *BoardService) BoardOverview(ctx context.Context, boardIDs []uuid.UUID, user uuid.UUID) ([]*dto.BoardOverview, error) { + log := logger.FromContext(ctx) OverviewBoards := make([]*dto.BoardOverview, len(boardIDs)) for i, id := range boardIDs { board, sessions, columns, err := s.database.GetBoardOverview(id) if err != nil { + log.Errorw("unable to get board overview", "board", id, "err", err) return nil, err } participantNum := len(sessions) @@ -133,10 +133,11 @@ func (s *BoardService) BoardOverview(_ context.Context, boardIDs []uuid.UUID, us return OverviewBoards, nil } -func (s *BoardService) Delete(_ context.Context, id uuid.UUID) error { +func (s *BoardService) Delete(ctx context.Context, id uuid.UUID) error { + log := logger.FromContext(ctx) err := s.database.DeleteBoard(id) if err != nil { - logger.Get().Errorw("unable to delete board", "err", err) + log.Errorw("unable to delete board", "err", err) } return err } @@ -185,7 +186,8 @@ func (s *BoardService) Update(ctx context.Context, body dto.BoardUpdateRequest) return new(dto.Board).From(board), err } -func (s *BoardService) SetTimer(_ context.Context, id uuid.UUID, minutes uint8) (*dto.Board, error) { +func (s *BoardService) SetTimer(ctx context.Context, id uuid.UUID, minutes uint8) (*dto.Board, error) { + log := logger.FromContext(ctx) timerStart := time.Now().Local() timerEnd := timerStart.Add(time.Minute * time.Duration(minutes)) update := database.BoardTimerUpdate{ @@ -195,7 +197,7 @@ func (s *BoardService) SetTimer(_ context.Context, id uuid.UUID, minutes uint8) } board, err := s.database.UpdateBoardTimer(update) if err != nil { - logger.Get().Errorw("unable to update board timer", "err", err) + log.Errorw("unable to update board timer", "err", err) return nil, err } s.UpdatedBoardTimer(board) @@ -203,7 +205,8 @@ func (s *BoardService) SetTimer(_ context.Context, id uuid.UUID, minutes uint8) return new(dto.Board).From(board), err } -func (s *BoardService) DeleteTimer(_ context.Context, id uuid.UUID) (*dto.Board, error) { +func (s *BoardService) DeleteTimer(ctx context.Context, id uuid.UUID) (*dto.Board, error) { + log := logger.FromContext(ctx) update := database.BoardTimerUpdate{ ID: id, TimerStart: nil, @@ -211,7 +214,7 @@ func (s *BoardService) DeleteTimer(_ context.Context, id uuid.UUID) (*dto.Board, } board, err := s.database.UpdateBoardTimer(update) if err != nil { - logger.Get().Errorw("unable to update board timer", "err", err) + log.Errorw("unable to update board timer", "err", err) return nil, err } s.UpdatedBoardTimer(board) @@ -219,9 +222,11 @@ func (s *BoardService) DeleteTimer(_ context.Context, id uuid.UUID) (*dto.Board, return new(dto.Board).From(board), err } -func (s *BoardService) IncrementTimer(_ context.Context, id uuid.UUID) (*dto.Board, error) { +func (s *BoardService) IncrementTimer(ctx context.Context, id uuid.UUID) (*dto.Board, error) { + log := logger.FromContext(ctx) board, err := s.database.GetBoard(id) if err != nil { + log.Errorw("unable to get board", "boardID", id, "err", err) return nil, err } @@ -246,7 +251,7 @@ func (s *BoardService) IncrementTimer(_ context.Context, id uuid.UUID) (*dto.Boa board, err = s.database.UpdateBoardTimer(update) if err != nil { - logger.Get().Errorw("unable to update board timer", "err", err) + log.Errorw("unable to update board timer", "err", err) return nil, err } s.UpdatedBoardTimer(board) @@ -255,26 +260,19 @@ func (s *BoardService) IncrementTimer(_ context.Context, id uuid.UUID) (*dto.Boa } func (s *BoardService) UpdatedBoardTimer(board database.Board) { - err := s.realtime.BroadcastToBoard(board.ID, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board.ID, realtime.BoardEvent{ Type: realtime.BoardEventBoardTimerUpdated, Data: new(dto.Board).From(board), }) - if err != nil { - logger.Get().Errorw("unable to broadcast updated timer", "err", err) - } } func (s *BoardService) UpdatedBoard(board database.Board) { - err := s.realtime.BroadcastToBoard(board.ID, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board.ID, realtime.BoardEvent{ Type: realtime.BoardEventBoardUpdated, Data: new(dto.Board).From(board), }) - if err != nil { - logger.Get().Errorw("unable to broadcast updated board", "err", err) - } - var err_msg string - err_msg, err = s.SyncBoardSettingChange(board.ID) + err_msg, err := s.SyncBoardSettingChange(board.ID) if err != nil { logger.Get().Errorw(err_msg, "err", err) } @@ -310,10 +308,7 @@ func (s *BoardService) SyncBoardSettingChange(boardID uuid.UUID) (string, error) } func (s *BoardService) DeletedBoard(board uuid.UUID) { - err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventBoardDeleted, }) - if err != nil { - logger.Get().Errorw("unable to broadcast deleted board", "err", err) - } } diff --git a/server/src/services/boards/columns.go b/server/src/services/boards/columns.go index 8f01eafbd7..f888f22664 100644 --- a/server/src/services/boards/columns.go +++ b/server/src/services/boards/columns.go @@ -3,6 +3,7 @@ package boards import ( "context" "database/sql" + "errors" "fmt" "github.com/google/uuid" @@ -15,30 +16,49 @@ import ( "scrumlr.io/server/logger" ) -func (s *BoardService) CreateColumn(_ context.Context, body dto.ColumnRequest) (*dto.Column, error) { +func (s *BoardService) CreateColumn(ctx context.Context, body dto.ColumnRequest) (*dto.Column, error) { + log := logger.FromContext(ctx) column, err := s.database.CreateColumn(database.ColumnInsert{Board: body.Board, Name: body.Name, Description: body.Description, Color: body.Color, Visible: body.Visible, Index: body.Index}) if err != nil { - logger.Get().Errorw("unable to create column", "err", err) + log.Errorw("unable to create column", "err", err) return nil, err } s.UpdatedColumns(body.Board) return new(dto.Column).From(column), err } -func (s *BoardService) DeleteColumn(_ context.Context, board, column, user uuid.UUID) error { - err := s.database.DeleteColumn(board, column, user) +func (s *BoardService) DeleteColumn(ctx context.Context, board, column, user uuid.UUID) error { + log := logger.FromContext(ctx) + + voting, err := s.database.GetOpenVoting(board) + var toBeDeletedVotes []database.Vote + if err != nil { + if !errors.Is(err, sql.ErrNoRows) { + log.Errorw("unable to get open voting", "board", board, "err", err) + return err + } + } else { + toBeDeletedVotes, err = s.database.GetVotes(filter.VoteFilter{Board: board, Voting: &voting.ID}) + if err != nil { + logger.Get().Errorw("unable to retrieve votes in deleted column", "err", err, "board", board, "column", column) + return err + } + } + + err = s.database.DeleteColumn(board, column, user) if err != nil { - logger.Get().Errorw("unable to delete column", "err", err) + log.Errorw("unable to delete column", "err", err) return err } - s.DeletedColumn(user, board, column) + s.DeletedColumn(user, board, column, toBeDeletedVotes) return err } -func (s *BoardService) UpdateColumn(_ context.Context, body dto.ColumnUpdateRequest) (*dto.Column, error) { +func (s *BoardService) UpdateColumn(ctx context.Context, body dto.ColumnUpdateRequest) (*dto.Column, error) { + log := logger.FromContext(ctx) column, err := s.database.UpdateColumn(database.ColumnUpdate{ID: body.ID, Board: body.Board, Name: body.Name, Description: body.Description, Color: body.Color, Visible: body.Visible, Index: body.Index}) if err != nil { - logger.Get().Errorw("unable to update column", "err", err) + log.Errorw("unable to update column", "err", err) return nil, err } s.UpdatedColumns(body.Board) @@ -74,13 +94,11 @@ func (s *BoardService) UpdatedColumns(board uuid.UUID) { logger.Get().Errorw("unable to retrieve columns in updated notes", "err", err) return } - err = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventColumnsUpdated, Data: dto.Columns(dbColumns), }) - if err != nil { - logger.Get().Errorw("unable to broadcast updated columns", "err", err) - } + var err_msg string err_msg, err = s.SyncNotesOnColumnChange(board) if err != nil { @@ -117,14 +135,11 @@ func (s *BoardService) SyncNotesOnColumnChange(boardID uuid.UUID) (string, error return "", err } -func (s *BoardService) DeletedColumn(user, board, column uuid.UUID) { - err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ +func (s *BoardService) DeletedColumn(user, board, column uuid.UUID, toBeDeletedVotes []database.Vote) { + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventColumnDeleted, Data: column, }) - if err != nil { - logger.Get().Errorw("unable to broadcast updated columns", "err", err) - } dbNotes, err := s.database.GetNotes(board) if err != nil { @@ -135,30 +150,14 @@ func (s *BoardService) DeletedColumn(user, board, column uuid.UUID) { for index, note := range dbNotes { eventNotes[index] = *new(dto.Note).From(note) } - err = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventNotesUpdated, Data: eventNotes, }) - if err != nil { - logger.Get().Errorw("unable to broadcast updated notes", "err", err) - } - - boardVotes, err := s.database.GetVotes(filter.VoteFilter{Board: board}) - if err != nil { - logger.Get().Errorw("unable to retrieve votes in deleted column", "err", err) - return - } - personalVotes := []*dto.Vote{} - for _, vote := range boardVotes { - if vote.User == user { - personalVotes = append(personalVotes, new(dto.Vote).From(vote)) - } - } - err = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ - Type: realtime.BoardEventVotesUpdated, - Data: personalVotes, - }) - if err != nil { - logger.Get().Errorw("unable to broadcast updated votes", "err", err) + if len(toBeDeletedVotes) > 0 { + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + Type: realtime.BoardEventVotesDeleted, + Data: toBeDeletedVotes, + }) } } diff --git a/server/src/services/boards/sessions.go b/server/src/services/boards/sessions.go index a3c7da0988..e11bc1b3fa 100644 --- a/server/src/services/boards/sessions.go +++ b/server/src/services/boards/sessions.go @@ -5,7 +5,6 @@ import ( "database/sql" "errors" "fmt" - "github.com/google/uuid" "scrumlr.io/server/common" @@ -102,13 +101,23 @@ func (s *BoardSessionService) Get(ctx context.Context, boardID, userID uuid.UUID return new(dto.BoardSession).From(session), err } -func (s *BoardSessionService) Update(_ context.Context, body dto.BoardSessionUpdateRequest) (*dto.BoardSession, error) { - sessionOfCaller, _ := s.database.GetBoardSession(body.Board, body.Caller) +func (s *BoardSessionService) Update(ctx context.Context, body dto.BoardSessionUpdateRequest) (*dto.BoardSession, error) { + log := logger.FromContext(ctx) + sessionOfCaller, err := s.database.GetBoardSession(body.Board, body.Caller) + if err != nil { + log.Errorw("unable to get board session", "board", body.Board, "calling user", body.Caller, "error", err) + return nil, fmt.Errorf("unable to get session for board: %w", err) + } if sessionOfCaller.Role == types.SessionRoleParticipant && body.User != body.Caller { + return nil, common.ForbiddenError(errors.New("not allowed to change other users session")) } - sessionOfUserToModify, _ := s.database.GetBoardSession(body.Board, body.User) + sessionOfUserToModify, err := s.database.GetBoardSession(body.Board, body.User) + if err != nil { + log.Errorw("unable to get board session", "board", body.Board, "target user", body.User, "error", err) + return nil, fmt.Errorf("unable to get session for board: %w", err) + } if body.Role != nil { if sessionOfCaller.Role == types.SessionRoleParticipant && *body.Role != types.SessionRoleParticipant { return nil, common.ForbiddenError(errors.New("cannot promote role")) @@ -129,6 +138,7 @@ func (s *BoardSessionService) Update(_ context.Context, body dto.BoardSessionUpd Banned: body.Banned, }) if err != nil { + log.Errorw("unable to update board session", "board", body.Board, "error", err) return nil, err } s.UpdatedSession(body.Board, session) @@ -202,12 +212,14 @@ func (s *BoardSessionService) ListSessionRequest(ctx context.Context, boardID uu return dto.BoardSessionRequests(requests), nil } -func (s *BoardSessionService) CreateSessionRequest(_ context.Context, boardID, userID uuid.UUID) (*dto.BoardSessionRequest, error) { +func (s *BoardSessionService) CreateSessionRequest(ctx context.Context, boardID, userID uuid.UUID) (*dto.BoardSessionRequest, error) { + log := logger.FromContext(ctx) request, err := s.database.CreateBoardSessionRequest(database.BoardSessionRequestInsert{ Board: boardID, User: userID, }) if err != nil { + log.Errorw("unable to create BoardSessionRequest", "board", boardID, "user", userID, "error", err) return nil, err } s.CreatedSessionRequest(boardID, request) @@ -215,9 +227,11 @@ func (s *BoardSessionService) CreateSessionRequest(_ context.Context, boardID, u return new(dto.BoardSessionRequest).From(request), err } -func (s *BoardSessionService) UpdateSessionRequest(_ context.Context, body dto.BoardSessionRequestUpdate) (*dto.BoardSessionRequest, error) { +func (s *BoardSessionService) UpdateSessionRequest(ctx context.Context, body dto.BoardSessionRequestUpdate) (*dto.BoardSessionRequest, error) { + log := logger.FromContext(ctx) request, err := s.database.UpdateBoardSessionRequest(database.BoardSessionRequestUpdate{Board: body.Board, User: body.User, Status: body.Status}) if err != nil { + log.Errorw("unable to update BoardSessionRequest", "board", body.Board, "user", body.User, "error", err) return nil, err } s.UpdatedSessionRequest(body.Board, request) @@ -226,13 +240,10 @@ func (s *BoardSessionService) UpdateSessionRequest(_ context.Context, body dto.B } func (s *BoardSessionService) CreatedSessionRequest(board uuid.UUID, request database.BoardSessionRequest) { - err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventSessionRequestCreated, Data: new(dto.BoardSessionRequest).From(request), }) - if err != nil { - logger.Get().Errorw("unable to broadcast created board session request", "err", err) - } } func (s *BoardSessionService) UpdatedSessionRequest(board uuid.UUID, request database.BoardSessionRequest) { @@ -244,35 +255,28 @@ func (s *BoardSessionService) UpdatedSessionRequest(board uuid.UUID, request dat } if status != "" { - err := s.realtime.BroadcastUpdateOnBoardSessionRequest(board, request.User, status) - if err != nil { - logger.Get().Errorw("unable to broadcast board session request with status", "status", status, "err", err) - } + _ = s.realtime.BroadcastUpdateOnBoardSessionRequest(board, request.User, status) } - err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventSessionRequestUpdated, Data: new(dto.BoardSessionRequest).From(request), }) - if err != nil { - logger.Get().Errorw("unable to broadcast updated board session request", "err", err) - } } func (s *BoardSessionService) CreatedSession(board uuid.UUID, session database.BoardSession) { - err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventParticipantCreated, Data: new(dto.BoardSession).From(session), }) - if err != nil { - logger.Get().Errorw("unable to broadcast created board session", "err", err) - } + } func (s *BoardSessionService) UpdatedSession(board uuid.UUID, session database.BoardSession) { connectedBoards, err := s.database.GetSingleUserConnectedBoards(session.User) if err != nil { + logger.Get().Errorw("unable to get user connections", "session", session, "error", err) return } for _, session := range connectedBoards { @@ -281,40 +285,31 @@ func (s *BoardSessionService) UpdatedSession(board uuid.UUID, session database.B logger.Get().Errorw("unable to get board session of user", "board", session.Board, "user", session.User, "err", err) return } - err = s.realtime.BroadcastToBoard(session.Board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(session.Board, realtime.BoardEvent{ Type: realtime.BoardEventParticipantUpdated, Data: new(dto.BoardSession).From(userSession), }) - if err != nil { - logger.Get().Errorw("unable to broadcast updated board session", "err", err) - } } // Sync columns columns, err := s.database.GetColumns(board) if err != nil { - logger.Get().Errorw("unable to get columns on a updatedsession call", "err", err) + logger.Get().Errorw("unable to get columns", "boardID", board, "err", err) } - err = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventColumnsUpdated, Data: dto.Columns(columns), }) - if err != nil { - logger.Get().Errorw("unable to broadcast update columns following a updatedsession call", "err", err) - } // Sync notes notes, err := s.database.GetNotes(board) if err != nil { logger.Get().Errorw("unable to get notes on a updatedsession call", "err", err) } - err = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventNotesSync, Data: dto.Notes(notes), }) - if err != nil { - logger.Get().Errorw("unable to broadcast sync notes following a updatedsession call", "err", err) - } } func (s *BoardSessionService) UpdatedSessions(board uuid.UUID, sessions []database.BoardSession) { @@ -322,11 +317,8 @@ func (s *BoardSessionService) UpdatedSessions(board uuid.UUID, sessions []databa for index, session := range sessions { eventSessions[index] = *new(dto.BoardSession).From(session) } - err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventParticipantsUpdated, Data: eventSessions, }) - if err != nil { - logger.Get().Errorw("unable to broadcast updated board sessions", "err", err) - } } diff --git a/server/src/services/notes/notes.go b/server/src/services/notes/notes.go index b6ee6ad950..e7dc85876d 100644 --- a/server/src/services/notes/notes.go +++ b/server/src/services/notes/notes.go @@ -3,7 +3,6 @@ package notes import ( "context" "database/sql" - "scrumlr.io/server/common" "scrumlr.io/server/identifiers" "scrumlr.io/server/services" @@ -25,11 +24,13 @@ type NoteService struct { type DB interface { CreateNote(insert database.NoteInsert) (database.Note, error) + ImportNote(note database.NoteImport) (database.Note, error) GetNote(id uuid.UUID) (database.Note, error) GetNotes(board uuid.UUID, columns ...uuid.UUID) ([]database.Note, error) UpdateNote(caller uuid.UUID, update database.NoteUpdate) (database.Note, error) DeleteNote(caller uuid.UUID, board uuid.UUID, id uuid.UUID, deleteStack bool) error GetVotes(f filter.VoteFilter) ([]database.Vote, error) + GetChildNotes(parentNote uuid.UUID) ([]database.Note, error) } func NewNoteService(db DB, rt *realtime.Broker) services.Notes { @@ -50,6 +51,26 @@ func (s *NoteService) Create(ctx context.Context, body dto.NoteCreateRequest) (* return new(dto.Note).From(note), err } +func (s *NoteService) Import(ctx context.Context, body dto.NoteImportRequest) (*dto.Note, error) { + log := logger.FromContext(ctx) + + note, err := s.database.ImportNote(database.NoteImport{ + Author: body.User, + Board: body.Board, + Position: &database.NoteUpdatePosition{ + Column: body.Position.Column, + Rank: body.Position.Rank, + Stack: body.Position.Stack, + }, + Text: body.Text, + }) + if err != nil { + log.Errorw("Could not import notes", "err", err) + return nil, err + } + return new(dto.Note).From(note), err +} + func (s *NoteService) Get(ctx context.Context, id uuid.UUID) (*dto.Note, error) { log := logger.FromContext(ctx) note, err := s.database.GetNote(id) @@ -103,34 +124,48 @@ func (s *NoteService) Update(ctx context.Context, body dto.NoteUpdateRequest) (* } func (s *NoteService) Delete(ctx context.Context, body dto.NoteDeleteRequest, id uuid.UUID) error { + log := logger.FromContext(ctx) user := ctx.Value(identifiers.UserIdentifier).(uuid.UUID) board := ctx.Value(identifiers.BoardIdentifier).(uuid.UUID) note := ctx.Value(identifiers.NoteIdentifier).(uuid.UUID) voteFilter := filter.VoteFilter{ - User: &user, Board: board, Note: ¬e, } + deletedVotes, err := s.database.GetVotes(voteFilter) + if body.DeleteStack { + stackedVotes, _ := s.database.GetChildNotes(note) + for _, n := range stackedVotes { + votes, err := s.database.GetVotes(filter.VoteFilter{ + Board: board, + Note: &n.ID, + }) + if err != nil { + log.Errorw("unable to get votes of stacked notes", "note", n, "error", err) + return err + } + deletedVotes = append(deletedVotes, votes...) + } + } - votes, vErr := s.database.GetVotes(voteFilter) - if vErr != nil { - logger.Get().Errorw("unable to retrieve votes for a note delete", "err", vErr) + if err != nil { + log.Errorw("unable to retrieve votes for a note delete", "err", err) } - err := s.database.DeleteNote(user, board, id, body.DeleteStack) + err = s.database.DeleteNote(user, board, id, body.DeleteStack) if err != nil { + log.Errorw("unable to delete note", "note", body, "err", err) return err } - s.DeletedNote(user, board, note, votes, body.DeleteStack) - + s.DeletedNote(user, board, note, deletedVotes, body.DeleteStack) return err } func (s *NoteService) UpdatedNotes(board uuid.UUID) { - notes, dbErr := s.database.GetNotes(board) - if dbErr != nil { - logger.Get().Errorw("unable to retrieve notes in UpdatedNotes call", "err", dbErr) + notes, err := s.database.GetNotes(board) + if err != nil { + logger.Get().Errorw("unable to retrieve notes in UpdatedNotes call", "boardID", board, "err", err) } eventNotes := make([]dto.Note, len(notes)) @@ -138,39 +173,25 @@ func (s *NoteService) UpdatedNotes(board uuid.UUID) { eventNotes[index] = *new(dto.Note).From(note) } - err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventNotesUpdated, Data: eventNotes, }) - if err != nil { - logger.Get().Errorw("unable to broadcast updated notes", "err", err) - } } -func (s *NoteService) DeletedNote(user, board, note uuid.UUID, votes []database.Vote, deleteStack bool) { +func (s *NoteService) DeletedNote(user, board, note uuid.UUID, deletedVotes []database.Vote, deleteStack bool) { noteData := map[string]interface{}{ "note": note, "deleteStack": deleteStack, } - err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventNoteDeleted, Data: noteData, }) - if err != nil { - logger.Get().Errorw("unable to broadcast updated notes", "err", err) - } - personalVotes := []*dto.Vote{} - for _, vote := range votes { - if vote.User == user { - personalVotes = append(personalVotes, new(dto.Vote).From(vote)) - } - } - err = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ - Type: realtime.BoardEventVotesUpdated, - Data: personalVotes, + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + Type: realtime.BoardEventVotesDeleted, + Data: deletedVotes, }) - if err != nil { - logger.Get().Errorw("unable to broadcast updated votes", "err", err) - } + } diff --git a/server/src/services/notes/notes_test.go b/server/src/services/notes/notes_test.go index 6a2474d7e4..8053112f82 100644 --- a/server/src/services/notes/notes_test.go +++ b/server/src/services/notes/notes_test.go @@ -2,6 +2,7 @@ package notes import ( "context" + "scrumlr.io/server/logger" "scrumlr.io/server/identifiers" "scrumlr.io/server/realtime" @@ -79,6 +80,10 @@ func (m *DBMock) GetVotes(f filter.VoteFilter) ([]database.Vote, error) { args := m.Called(f) return args.Get(0).([]database.Vote), args.Error(1) } +func (m *DBMock) GetChildNotes(note uuid.UUID) ([]database.Note, error) { + args := m.Called(note) + return args.Get(0).([]database.Note), args.Error(1) +} func TestNoteServiceTestSuite(t *testing.T) { suite.Run(t, new(NoteServiceTestSuite)) @@ -115,7 +120,7 @@ func (suite *NoteServiceTestSuite) TestCreate() { clientMock.On("Publish", publishSubject, publishEvent).Return(nil) - _, err := s.Create(context.Background(), dto.NoteCreateRequest{ + _, err := s.Create(logger.InitTestLogger(context.Background()), dto.NoteCreateRequest{ User: authorID, Board: boardID, Column: colID, @@ -138,7 +143,7 @@ func (suite *NoteServiceTestSuite) TestGetNote() { ID: noteID, }, nil) - _, err := s.Get(context.Background(), noteID) + _, err := s.Get(logger.InitTestLogger(context.Background()), noteID) assert.NoError(suite.T(), err) mock.AssertExpectations(suite.T()) @@ -153,7 +158,7 @@ func (suite *NoteServiceTestSuite) TestGetNotes() { mock.On("GetNotes", boardID).Return([]database.Note{}, nil) - _, err := s.List(context.Background(), boardID) + _, err := s.List(logger.InitTestLogger(context.Background()), boardID) assert.NoError(suite.T(), err) mock.AssertExpectations(suite.T()) @@ -195,8 +200,10 @@ func (suite *NoteServiceTestSuite) TestUpdateNote() { // Mock for the updatedNotes call, which internally calls GetNotes mock.On("GetNotes", boardID).Return([]database.Note{}, nil) - ctx := context.Background() + ctx := logger.InitTestLogger(context.Background()) + ctx = context.WithValue(ctx, identifiers.UserIdentifier, callerID) + mock.On("UpdateNote", callerID, database.NoteUpdate{ ID: noteID, Board: boardID, @@ -235,7 +242,6 @@ func (suite *NoteServiceTestSuite) TestDeleteNote() { DeleteStack: deleteStack, } voteFilter := filter.VoteFilter{ - User: &callerID, Board: boardID, Note: ¬eID, } @@ -250,19 +256,23 @@ func (suite *NoteServiceTestSuite) TestDeleteNote() { Type: realtime.BoardEventNoteDeleted, Data: deletedNoteRealTimeUpdate, } - publishEventVotesUpdated := realtime.BoardEvent{ - Type: realtime.BoardEventVotesUpdated, - Data: []*dto.Vote{}, + publishEventVotesDeleted := realtime.BoardEvent{ + Type: realtime.BoardEventVotesDeleted, + Data: []database.Vote{}, } clientMock.On("Publish", publishSubject, publishEventNoteDeleted).Return(nil) - clientMock.On("Publish", publishSubject, publishEventVotesUpdated).Return(nil) + clientMock.On("Publish", publishSubject, publishEventVotesDeleted).Return(nil) - ctx := context.Background() + ctx := logger.InitTestLogger(context.Background()) ctx = context.WithValue(ctx, identifiers.UserIdentifier, callerID) ctx = context.WithValue(ctx, identifiers.BoardIdentifier, boardID) ctx = context.WithValue(ctx, identifiers.NoteIdentifier, noteID) mock.On("GetVotes", voteFilter).Return([]database.Vote{}, nil) + if deleteStack { + mock.On("GetChildNotes", noteID).Return([]database.Note{}, nil) + mock.On("GetVotes", voteFilter).Return([]database.Vote{}, nil) + } mock.On("DeleteNote", callerID, boardID, noteID, deleteStack).Return(nil) err := s.Delete(ctx, body, noteID) @@ -293,7 +303,7 @@ func (suite *NoteServiceTestSuite) TestBadInputOnCreate() { Text: txt, }).Return(database.Note{}, aDBError) - _, err := s.Create(context.Background(), dto.NoteCreateRequest{ + _, err := s.Create(logger.InitTestLogger(context.Background()), dto.NoteCreateRequest{ User: authorID, Board: boardID, Column: colID, @@ -313,7 +323,7 @@ func (suite *NoteServiceTestSuite) TestNoEntryOnGetNote() { expectedAPIError := &common.APIError{StatusCode: http.StatusNotFound, StatusText: "Resource not found."} mock.On("GetNote", boardID).Return(database.Note{}, sql.ErrNoRows) - _, err := s.Get(context.Background(), boardID) + _, err := s.Get(logger.InitTestLogger(context.Background()), boardID) assert.Error(suite.T(), err) assert.Equal(suite.T(), expectedAPIError, err) diff --git a/server/src/services/reactions/reactions.go b/server/src/services/reactions/reactions.go index 5929af88e6..2ea0f9b5c5 100644 --- a/server/src/services/reactions/reactions.go +++ b/server/src/services/reactions/reactions.go @@ -33,14 +33,23 @@ func NewReactionService(db DB, rt *realtime.Broker) services.Reactions { return b } -func (s *ReactionService) List(_ context.Context, boardID uuid.UUID) ([]*dto.Reaction, error) { +func (s *ReactionService) List(ctx context.Context, boardID uuid.UUID) ([]*dto.Reaction, error) { + log := logger.FromContext(ctx) reactions, err := s.database.GetReactions(boardID) - + if err != nil { + log.Errorw("unable to get reactions", "boardID", boardID, "err", err) + return nil, err + } return dto.Reactions(reactions), err } -func (s *ReactionService) Get(_ context.Context, id uuid.UUID) (*dto.Reaction, error) { +func (s *ReactionService) Get(ctx context.Context, id uuid.UUID) (*dto.Reaction, error) { + log := logger.FromContext(ctx) reaction, err := s.database.GetReaction(id) + if err != nil { + log.Errorw("unable to get reaction", "userID", id, "err", err) + return nil, err + } return new(dto.Reaction).From(reaction), err } @@ -65,10 +74,11 @@ func (s *ReactionService) Create(ctx context.Context, board uuid.UUID, body dto. return new(dto.Reaction).From(reaction), err } -func (s *ReactionService) Delete(_ context.Context, board, user, id uuid.UUID) error { +func (s *ReactionService) Delete(ctx context.Context, board, user, id uuid.UUID) error { + log := logger.FromContext(ctx) err := s.database.RemoveReaction(board, user, id) if err != nil { - logger.Get().Errorw("unable to remove reaction", "board", board, "user", user, "reaction", id) + log.Errorw("unable to remove reaction", "board", board, "user", user, "reaction", id) return err } @@ -98,36 +108,24 @@ func (s *ReactionService) Update(ctx context.Context, board, user, id uuid.UUID, func (s *ReactionService) AddedReaction(board uuid.UUID, reaction database.Reaction) { eventReaction := *new(dto.Reaction).From(reaction) - err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventReactionAdded, Data: eventReaction, }) - - if err != nil { - logger.Get().Errorw("unable to broadcast updated reactions", "err", err) - } } func (s *ReactionService) DeletedReaction(board, reaction uuid.UUID) { - err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventReactionDeleted, Data: reaction, }) - - if err != nil { - logger.Get().Errorw("unable to broadcast deleted reaction", "err", err) - } } func (s *ReactionService) UpdatedReaction(board uuid.UUID, reaction database.Reaction) { eventReaction := *new(dto.Reaction).From(reaction) - err := s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventReactionUpdated, Data: eventReaction, }) - - if err != nil { - logger.Get().Errorw("unable to broadcast updated reaction", "err", err) - } } diff --git a/server/src/services/services.go b/server/src/services/services.go index 60ae210ece..5e58d37299 100644 --- a/server/src/services/services.go +++ b/server/src/services/services.go @@ -16,6 +16,7 @@ type Users interface { CreateMicrosoftUser(ctx context.Context, id, name, avatarUrl string) (*dto.User, error) CreateAzureAdUser(ctx context.Context, id, name, avatarUrl string) (*dto.User, error) CreateAppleUser(ctx context.Context, id, name, avatarUrl string) (*dto.User, error) + CreateOIDCUser(ctx context.Context, id, name, avatarUrl string) (*dto.User, error) Update(ctx context.Context, body dto.UserUpdateRequest) (*dto.User, error) } @@ -40,7 +41,7 @@ type Boards interface { GetColumn(ctx context.Context, boardID, columnID uuid.UUID) (*dto.Column, error) ListColumns(ctx context.Context, boardID uuid.UUID) ([]*dto.Column, error) - FullBoard(ctx context.Context, boardID uuid.UUID) (*dto.Board, []*dto.BoardSessionRequest, []*dto.BoardSession, []*dto.Column, []*dto.Note, []*dto.Reaction, []*dto.Voting, []*dto.Vote, error) + FullBoard(ctx context.Context, boardID uuid.UUID) (*dto.FullBoard, error) BoardOverview(ctx context.Context, boardIDs []uuid.UUID, user uuid.UUID) ([]*dto.BoardOverview, error) GetBoards(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error) } @@ -67,6 +68,7 @@ type BoardSessions interface { type Notes interface { Create(ctx context.Context, body dto.NoteCreateRequest) (*dto.Note, error) + Import(ctx context.Context, body dto.NoteImportRequest) (*dto.Note, error) Get(ctx context.Context, id uuid.UUID) (*dto.Note, error) Update(ctx context.Context, body dto.NoteUpdateRequest) (*dto.Note, error) List(ctx context.Context, id uuid.UUID) ([]*dto.Note, error) @@ -104,9 +106,13 @@ type BoardReactions interface { type BoardTemplates interface { Create(ctx context.Context, body dto.CreateBoardTemplateRequest) (*dto.BoardTemplate, error) Get(ctx context.Context, id uuid.UUID) (*dto.BoardTemplate, error) - List(ctx context.Context, user uuid.UUID) ([]*dto.BoardTemplate, error) + List(ctx context.Context, user uuid.UUID) ([]*dto.BoardTemplateFull, error) Update(ctx context.Context, body dto.BoardTemplateUpdateRequest) (*dto.BoardTemplate, error) Delete(ctx context.Context, id uuid.UUID) error - ListTemplateColumns(ctx context.Context, boardTemplateID uuid.UUID) ([]*dto.ColumnTemplate, error) + CreateColumnTemplate(ctx context.Context, body dto.ColumnTemplateRequest) (*dto.ColumnTemplate, error) + GetColumnTemplate(ctx context.Context, boardID, columnID uuid.UUID) (*dto.ColumnTemplate, error) + ListColumnTemplates(ctx context.Context, board uuid.UUID) ([]*dto.ColumnTemplate, error) + UpdateColumnTemplate(ctx context.Context, body dto.ColumnTemplateUpdateRequest) (*dto.ColumnTemplate, error) + DeleteColumnTemplate(ctx context.Context, boar, column, user uuid.UUID) error } diff --git a/server/src/services/users/users.go b/server/src/services/users/users.go index 29262c4b5f..203584341b 100644 --- a/server/src/services/users/users.go +++ b/server/src/services/users/users.go @@ -42,7 +42,7 @@ func (s *UserService) Get(ctx context.Context, userID uuid.UUID) (*dto.User, err return new(dto.User).From(user), err } -func (s *UserService) LoginAnonymous(_ context.Context, name string) (*dto.User, error) { +func (s *UserService) LoginAnonymous(ctx context.Context, name string) (*dto.User, error) { user, err := s.database.CreateAnonymousUser(name) return new(dto.User).From(user), err } @@ -72,18 +72,29 @@ func (s *UserService) CreateAppleUser(_ context.Context, id, name, avatarUrl str return new(dto.User).From(user), err } -func (s *UserService) Update(_ context.Context, body dto.UserUpdateRequest) (*dto.User, error) { +func (s *UserService) CreateOIDCUser(_ context.Context, id, name, avatarUrl string) (*dto.User, error) { + user, err := s.database.CreateOIDCUser(id, name, avatarUrl) + return new(dto.User).From(user), err +} + +func (s *UserService) Update(ctx context.Context, body dto.UserUpdateRequest) (*dto.User, error) { + log := logger.FromContext(ctx) user, err := s.database.UpdateUser(database.UserUpdate{ ID: body.ID, Name: body.Name, Avatar: body.Avatar, }) - s.UpdatedUser(user) + if err != nil { + log.Errorw("unable to update user", "user", body.ID, "err", err) + return nil, err + } + s.UpdatedUser(user) return new(dto.User).From(user), err } func (s *UserService) UpdatedUser(user database.User) { + connectedBoards, err := s.database.GetSingleUserConnectedBoards(user.ID) if err != nil { return @@ -93,13 +104,9 @@ func (s *UserService) UpdatedUser(user database.User) { if err != nil { logger.Get().Errorw("unable to get board session", "board", userSession.Board, "user", userSession.User.ID(), "err", err) } - err = s.realtime.BroadcastToBoard(session.Board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(session.Board, realtime.BoardEvent{ Type: realtime.BoardEventParticipantUpdated, Data: new(dto.BoardSession).From(session), }) - - if err != nil { - logger.Get().Errorw("unable to broadcast updated user", "err", err) - } } } diff --git a/server/src/services/votings/votes.go b/server/src/services/votings/votes.go index 5f0227398e..65c000d64b 100644 --- a/server/src/services/votings/votes.go +++ b/server/src/services/votings/votes.go @@ -23,12 +23,20 @@ func (s *VotingService) AddVote(ctx context.Context, body dto.VoteRequest) (*dto return new(dto.Vote).From(v), err } -func (s *VotingService) RemoveVote(_ context.Context, body dto.VoteRequest) error { - return s.database.RemoveVote(body.Board, body.User, body.Note) +func (s *VotingService) RemoveVote(ctx context.Context, body dto.VoteRequest) error { + log := logger.FromContext(ctx) + err := s.database.RemoveVote(body.Board, body.User, body.Note) + if err != nil { + log.Errorw("unable to remove vote", "board", body.Board, "user", body.User) + } + return err } -func (s *VotingService) GetVotes(_ context.Context, f filter.VoteFilter) ([]*dto.Vote, error) { +func (s *VotingService) GetVotes(ctx context.Context, f filter.VoteFilter) ([]*dto.Vote, error) { + log := logger.FromContext(ctx) votes, err := s.database.GetVotes(f) + if err != nil { + log.Errorw("unable to get votes", "err", err) + } return dto.Votes(votes), err - } diff --git a/server/src/services/votings/votings.go b/server/src/services/votings/votings.go index 4def1091b4..dfd1f92e80 100644 --- a/server/src/services/votings/votings.go +++ b/server/src/services/votings/votings.go @@ -31,8 +31,8 @@ type DB interface { GetVotes(f filter.VoteFilter) ([]database.Vote, error) AddVote(board, user, note uuid.UUID) (database.Vote, error) RemoveVote(board, user, note uuid.UUID) error - GetNotes(board uuid.UUID, columns ...uuid.UUID) ([]database.Note, error) + GetOpenVoting(board uuid.UUID) (database.Voting, error) } func NewVotingService(db DB, rt *realtime.Broker) services.Votings { @@ -84,7 +84,11 @@ func (s *VotingService) Update(ctx context.Context, body dto.VotingUpdateRequest } if voting.Status == types.VotingStatusClosed { - votes, _ := s.getVotes(ctx, body.Board, body.ID) + votes, err := s.getVotes(ctx, body.Board, body.ID) + if err != nil { + log.Errorw("unable to get votes", "err", err) + return nil, err + } s.UpdatedVoting(body.Board, voting.ID) return new(dto.Voting).From(voting, votes), err } @@ -104,19 +108,34 @@ func (s *VotingService) Get(ctx context.Context, boardID, id uuid.UUID) (*dto.Vo } if voting.Status == types.VotingStatusClosed { - votes, _ := s.getVotes(ctx, boardID, id) + votes, err := s.getVotes(ctx, boardID, id) + if err != nil { + log.Errorw("unable to get votes", "voting", id, "error", err) + return nil, err + } return new(dto.Voting).From(voting, votes), err } return new(dto.Voting).From(voting, nil), err } -func (s *VotingService) List(_ context.Context, boardID uuid.UUID) ([]*dto.Voting, error) { +func (s *VotingService) List(ctx context.Context, boardID uuid.UUID) ([]*dto.Voting, error) { + log := logger.FromContext(ctx) votings, votes, err := s.database.GetVotings(boardID) + if err != nil { + log.Errorw("unable to get votings", "board", boardID, "error", err) + return nil, err + } return dto.Votings(votings, votes), err } -func (s *VotingService) getVotes(_ context.Context, boardID, id uuid.UUID) ([]database.Vote, error) { - return s.database.GetVotes(filter.VoteFilter{Board: boardID, Voting: &id}) +func (s *VotingService) getVotes(ctx context.Context, boardID, id uuid.UUID) ([]database.Vote, error) { + log := logger.FromContext(ctx) + votes, err := s.database.GetVotes(filter.VoteFilter{Board: boardID, Voting: &id}) + if err != nil { + log.Errorw("unable to get votes", "voting", id, "error", err) + return nil, err + } + return votes, err } func (s *VotingService) CreatedVoting(board, voting uuid.UUID) { @@ -126,13 +145,10 @@ func (s *VotingService) CreatedVoting(board, voting uuid.UUID) { return } - err = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventVotingCreated, Data: new(dto.Voting).From(dbVoting, nil), }) - if err != nil { - logger.Get().Errorw("unable to broadcast created voting", "err", err) - } } func (s *VotingService) UpdatedVoting(board, voting uuid.UUID) { @@ -143,10 +159,13 @@ func (s *VotingService) UpdatedVoting(board, voting uuid.UUID) { return } if dbVoting.Status == types.VotingStatusClosed { - notes, _ = s.database.GetNotes(board) + notes, err = s.database.GetNotes(board) + if err != nil { + logger.Get().Errorw("unable to retrieve notes in updated voting", "err", err) + } } - err = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ + _ = s.realtime.BroadcastToBoard(board, realtime.BoardEvent{ Type: realtime.BoardEventVotingUpdated, Data: struct { Voting *dto.Voting `json:"voting"` @@ -156,7 +175,5 @@ func (s *VotingService) UpdatedVoting(board, voting uuid.UUID) { Notes: dto.Notes(notes), }, }) - if err != nil { - logger.Get().Errorw("unable to broadcast updated voting", "err", err) - } + } diff --git a/server/src/services/votings/votings_test.go b/server/src/services/votings/votings_test.go index 879c2b22f9..047ef1b3ec 100644 --- a/server/src/services/votings/votings_test.go +++ b/server/src/services/votings/votings_test.go @@ -2,7 +2,12 @@ package votings import ( "context" + "errors" + "math/rand/v2" + "scrumlr.io/server/common" + "scrumlr.io/server/logger" "testing" + "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" @@ -117,13 +122,109 @@ func (suite *votingServiceTestSuite) TestCreate() { mock.On("CreateVoting", database.VotingInsert{ Status: types.VotingStatusOpen, }).Return(database.Voting{}, nil) + mock.On("GetVoting", boardId, votingId).Return(database.Voting{}, []database.Vote{}, nil) rtClientMock.On("Publish", publishSubject, publishEvent).Return(nil) - - create, err := s.Create(context.Background(), votingRequest) + create, err := s.Create(logger.InitTestLogger(context.Background()), votingRequest) assert.NotNil(suite.T(), create) assert.NoError(suite.T(), err) mock.AssertExpectations(suite.T()) rtClientMock.AssertExpectations(suite.T()) } + +func (suite *votingServiceTestSuite) TestUpdateVoting() { + boardId := uuid.New() + votingID := uuid.New() + voteLimit := rand.IntN(10) + voting := database.Voting{ + ID: votingID, + Board: boardId, + CreatedAt: time.Now(), + VoteLimit: voteLimit, + AllowMultipleVotes: false, + ShowVotesOfOthers: false, + Status: types.VotingStatusClosed, + } + tests := []struct { + name string + err error + votingStatus types.VotingStatus + voting database.Voting + update *dto.Voting + }{ + { + name: "Voting status open", + err: common.BadRequestError(errors.New("not allowed ot change to open state")), + votingStatus: types.VotingStatusOpen, + voting: database.Voting{}, + update: nil, + }, + { + name: "Voting status closed", + err: nil, + votingStatus: types.VotingStatusClosed, + voting: voting, + update: new(dto.Voting).From(voting, nil), + }, + } + + for _, tt := range tests { + + suite.Run(tt.name, func() { + s := new(VotingService) + mock := new(DBMock) + s.database = mock + + rtClientMock := &mockRtClient{} + rtMock := &realtime.Broker{ + Con: rtClientMock, + } + s.realtime = rtMock + + updateVotingRequest := dto.VotingUpdateRequest{ + ID: votingID, + Board: boardId, + Status: tt.votingStatus, + } + + // Mocks for realtime + publishSubject := "board." + boardId.String() + publishEvent := realtime.BoardEvent{ + Type: realtime.BoardEventVotingUpdated, + Data: struct { + Voting *dto.Voting `json:"voting"` + Notes []*dto.Note `json:"notes"` + }{ + Voting: &dto.Voting{}, + Notes: nil, + }, + } + + if tt.votingStatus == types.VotingStatusClosed { + mock.On("UpdateVoting", database.VotingUpdate{ + ID: votingID, + Board: boardId, + Status: tt.votingStatus, + }).Return(tt.voting, tt.err) + + mock.On("GetVotes", filter.VoteFilter{ + Board: boardId, + Voting: &votingID, + }).Return([]database.Vote{}, nil) + + mock.On("GetVoting", boardId, votingID).Return(database.Voting{}, []database.Vote{}, nil) + + rtClientMock.On("Publish", publishSubject, publishEvent).Return(nil) + } + + update, err := s.Update(logger.InitTestLogger(context.Background()), updateVotingRequest) + + assert.Equal(suite.T(), tt.err, err) + assert.Equal(suite.T(), update, tt.update) + mock.AssertExpectations(suite.T()) + rtClientMock.AssertExpectations(suite.T()) + + }) + } +} diff --git a/src/api/auth.ts b/src/api/auth.ts index 276d5fa8f1..c83957bc75 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -1,5 +1,5 @@ import {SERVER_HTTP_URL} from "../config"; -import {AuthDto} from "../types/auth"; +import {AuthDto} from "../store/features/auth/types"; export const AuthAPI = { /** diff --git a/src/api/board.ts b/src/api/board.ts index 7caf3ed27c..14161809e3 100644 --- a/src/api/board.ts +++ b/src/api/board.ts @@ -1,5 +1,5 @@ import {Color} from "constants/colors"; -import {EditBoardRequest} from "types/board"; +import {Board, EditBoardRequest} from "store/features/board/types"; import {SERVER_HTTP_URL} from "../config"; export const BoardAPI = { @@ -35,6 +35,27 @@ export const BoardAPI = { throw new Error(`unable to create board: ${error}`); } }, + importBoard: async (boardJson: string) => { + try { + const response = await fetch(`${SERVER_HTTP_URL}/import`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + body: boardJson, + }); + + if (response.status === 201) { + const body = (await response.json()) as Board; + return body.id; + } + + throw new Error(`request resulted in response status ${response.status}`); + } catch (error) { + throw new Error(`unable to import board: ${error}`); + } + }, /** * Edits the board with the specified parameters. diff --git a/src/api/boardReaction.ts b/src/api/boardReaction.ts index ed80c3c3e9..135e7cfdc8 100644 --- a/src/api/boardReaction.ts +++ b/src/api/boardReaction.ts @@ -1,6 +1,6 @@ import {SERVER_HTTP_URL} from "config"; -import {ReactionType} from "types/reaction"; -import {BoardReactionType} from "types/boardReaction"; +import {ReactionType} from "store/features/reactions/types"; +import {BoardReactionType} from "store/features/boardReactions/types"; export const BoardReactionAPI = { addBoardReaction: async (boardId: string, reactionType: ReactionType) => { diff --git a/src/api/column.ts b/src/api/column.ts index 593144c0c2..83d40f410f 100644 --- a/src/api/column.ts +++ b/src/api/column.ts @@ -1,4 +1,4 @@ -import {EditColumnRequest} from "types/column"; +import {ColumnWithoutId} from "store/features/columns/types"; import {SERVER_HTTP_URL} from "../config"; export const ColumnAPI = { @@ -14,7 +14,7 @@ export const ColumnAPI = { * * @returns a {status, description} object */ - editColumn: async (boardId: string, columnId: string, column: EditColumnRequest) => { + editColumn: async (boardId: string, columnId: string, column: ColumnWithoutId) => { try { const response = await fetch(`${SERVER_HTTP_URL}/boards/${boardId}/columns/${columnId}`, { method: "PUT", diff --git a/src/api/info.ts b/src/api/info.ts index e10af5f3c5..8314c43017 100644 --- a/src/api/info.ts +++ b/src/api/info.ts @@ -1,6 +1,8 @@ -import {SERVER_HTTP_URL} from "../config"; +import {SERVER_HTTP_URL} from "config"; +import {ServerInfo} from "store/features"; -interface ServerInformation { +// type as received from the backend +interface ServerInformationDto { anonymousLoginDisabled: boolean; authProvider: string[]; serverTime: string; @@ -21,7 +23,14 @@ export const InfoAPI = { const response = await fetch(`${SERVER_HTTP_URL}/info`); if (response.status === 200) { - return (await response.json()) as ServerInformation; + const info = (await response.json()) as ServerInformationDto; + // convert to frontend type, don't ask me why they're different in the first place though + return { + serverTime: new Date(info.serverTime).getTime(), + enabledAuthProvider: info.authProvider, + anonymousLoginDisabled: info.anonymousLoginDisabled, + feedbackEnabled: info.feedbackEnabled, + } as ServerInfo; } throw new Error(`responded with status code ${response.status}`); diff --git a/src/api/note.ts b/src/api/note.ts index d46ecbff2b..8b90f9b011 100644 --- a/src/api/note.ts +++ b/src/api/note.ts @@ -1,5 +1,5 @@ import {SERVER_HTTP_URL} from "../config"; -import {EditNote, Note} from "../types/note"; +import {EditNote, Note} from "../store/features/notes/types"; export const NoteAPI = { /** diff --git a/src/api/participant.ts b/src/api/participant.ts index dd26d416b7..6177225235 100644 --- a/src/api/participant.ts +++ b/src/api/participant.ts @@ -1,4 +1,4 @@ -import {Participant} from "types/participant"; +import {Participant} from "store/features/participants/types"; import {SERVER_HTTP_URL} from "../config"; export const ParticipantsAPI = { diff --git a/src/api/reaction.ts b/src/api/reaction.ts index 1d8cef8804..45f8ff811e 100644 --- a/src/api/reaction.ts +++ b/src/api/reaction.ts @@ -1,5 +1,5 @@ import {SERVER_HTTP_URL} from "../config"; -import {Reaction, ReactionType} from "../types/reaction"; +import {Reaction, ReactionType} from "../store/features/reactions/types"; export const ReactionAPI = { addReaction: async (board: string, note: string, reactionType: ReactionType) => { diff --git a/src/api/request.ts b/src/api/request.ts index e635e93530..944911b236 100644 --- a/src/api/request.ts +++ b/src/api/request.ts @@ -1,4 +1,4 @@ -import {Request} from "types/request"; +import {Request} from "store/features/requests/types"; import {SERVER_HTTP_URL} from "../config"; export const RequestAPI = { diff --git a/src/api/user.ts b/src/api/user.ts index 85e259d84c..4b7a69de51 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -1,4 +1,4 @@ -import {Auth} from "types/auth"; +import {Auth} from "store/features/auth/types"; import {SERVER_HTTP_URL} from "../config"; export const UserAPI = { diff --git a/src/api/vote.ts b/src/api/vote.ts index 5cea3015db..6be94d1dff 100644 --- a/src/api/vote.ts +++ b/src/api/vote.ts @@ -1,5 +1,5 @@ import {SERVER_HTTP_URL} from "../config"; -import {Vote} from "../types/vote"; +import {Vote} from "../store/features/votes/types"; export const VoteAPI = { /** diff --git a/src/api/votings.ts b/src/api/votings.ts index 8c2384c701..7c1846766d 100644 --- a/src/api/votings.ts +++ b/src/api/votings.ts @@ -1,4 +1,4 @@ -import {CreateVotingRequest} from "types/voting"; +import {CreateVotingRequest} from "store/features/votings/types"; import {SERVER_HTTP_URL} from "../config"; export const VotingAPI = { diff --git a/src/assets/icon-info.svg b/src/assets/icon-info.svg new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/assets/icons/apple.svg b/src/assets/icons/apple.svg new file mode 100644 index 0000000000..3ce95ddf4f --- /dev/null +++ b/src/assets/icons/apple.svg @@ -0,0 +1,5 @@ + + + + diff --git a/src/assets/icons/azure.svg b/src/assets/icons/azure.svg new file mode 100644 index 0000000000..e53ffbd8c5 --- /dev/null +++ b/src/assets/icons/azure.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/icons/openid.svg b/src/assets/icons/openid.svg new file mode 100644 index 0000000000..8b96340b49 --- /dev/null +++ b/src/assets/icons/openid.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/AccessPolicySelection/AccessPolicySelection.tsx b/src/components/AccessPolicySelection/AccessPolicySelection.tsx index 78b284f105..3b7746cbc7 100644 --- a/src/components/AccessPolicySelection/AccessPolicySelection.tsx +++ b/src/components/AccessPolicySelection/AccessPolicySelection.tsx @@ -1,6 +1,6 @@ import {FC, useState} from "react"; import "./AccessPolicySelection.scss"; -import {AccessPolicy} from "types/board"; +import {AccessPolicy} from "store/features/board/types"; import {generateRandomString} from "utils/random"; import {useTranslation} from "react-i18next"; import {Visible, Hidden, Duplicate, Refresh} from "components/Icon"; @@ -22,16 +22,10 @@ export const AccessPolicySelection: FC = ({accessPol const {t} = useTranslation(); const [visiblePassphrase, setVisiblePassphrase] = useState(true); - const handlePolicyChange = (newAccessPolicy: AccessPolicy) => { - if (newAccessPolicy >= 0 && newAccessPolicy <= 2) { - onAccessPolicyChange(newAccessPolicy); - } - }; - let AccessPolicyDescription; let AdditionalAccessPolicySettings; switch (accessPolicy) { - case AccessPolicy.BY_PASSPHRASE: + case "BY_PASSPHRASE": AccessPolicyDescription = {t("AccessPolicySelection.byPassphrase")}; AdditionalAccessPolicySettings = ( <> @@ -68,10 +62,10 @@ export const AccessPolicySelection: FC = ({accessPol ); break; - case AccessPolicy.BY_INVITE: + case "BY_INVITE": AccessPolicyDescription = {t("AccessPolicySelection.manualVerification")}; break; - case AccessPolicy.PUBLIC: + case "PUBLIC": default: AccessPolicyDescription = {t("AccessPolicySelection.public")}; break; @@ -82,24 +76,20 @@ export const AccessPolicySelection: FC = ({accessPol

{t("AccessPolicySelection.title")}

- diff --git a/src/components/AccessPolicySelection/__tests__/AccessPolicySelection.test.tsx b/src/components/AccessPolicySelection/__tests__/AccessPolicySelection.test.tsx index be1f2f61ca..3ba6383325 100644 --- a/src/components/AccessPolicySelection/__tests__/AccessPolicySelection.test.tsx +++ b/src/components/AccessPolicySelection/__tests__/AccessPolicySelection.test.tsx @@ -1,24 +1,21 @@ import {fireEvent, waitFor} from "@testing-library/react"; import {AccessPolicySelection} from "components/AccessPolicySelection/AccessPolicySelection"; -import {AccessPolicy} from "types/board"; import {render} from "testUtils"; describe("AccessPolicySelection", () => { test("dont show passphrase input on default state", () => { - const {container} = render(); + const {container} = render(); expect(container.querySelector(`[data-testid="passphrase-input"]`)).toBeNull(); }); test("show passphrase input on access policy by passphrase", () => { - const {container} = render(); + const {container} = render(); expect(container.querySelector(`[data-testid="passphrase-input"]`)).toBeDefined(); }); test("trigger on change on passphrase change", async () => { const onChangeOfPassphrase = jest.fn(); - const {container} = render( - - ); + const {container} = render(); fireEvent.change(container.querySelector(`[data-testid="passphrase-input"]`)!, {target: {value: "1234"}}); @@ -29,9 +26,7 @@ describe("AccessPolicySelection", () => { test("trigger on password change when random generator is clicked", async () => { const onChangeOfPassphrase = jest.fn(); - const {container} = render( - - ); + const {container} = render(); fireEvent.click(container.querySelector(`[data-testid="random-passwort-generator"]`)!); diff --git a/src/components/Avatar/Avatar.tsx b/src/components/Avatar/Avatar.tsx index b400ad0b28..5844de6d4b 100644 --- a/src/components/Avatar/Avatar.tsx +++ b/src/components/Avatar/Avatar.tsx @@ -4,7 +4,6 @@ import _ from "underscore"; import {hashCode} from "utils/hash"; import "./Avatar.scss"; import classNames from "classnames"; -import {getColorClassName, getColorForIndex} from "../../constants/colors"; import { AVATAR_ACCESSORIES_TYPES, AVATAR_CLOTHE_COLORS, @@ -18,38 +17,12 @@ import { AVATAR_MOUTH_TYPES, AVATAR_SKIN_COLORS, AVATAR_TOP_TYPES, - AvatarAccessoriesType, - AvatarClotheColor, - AvatarClotheType, - AvatarEyebrowType, - AvatarEyeType, - AvatarFacialHairColor, - AvatarFacialHairType, - AvatarGraphicType, - AvatarHairColor, - AvatarMouthType, - AvatarSkinColor, - AvatarTopType, -} from "./types"; - -export interface AvataaarProps { - accentColorClass: string; - skinColor: AvatarSkinColor; - topType: AvatarTopType; - clotheColor: AvatarClotheColor; - graphicType: AvatarGraphicType; - clotheType: AvatarClotheType; - hairColor: AvatarHairColor; - facialHairColor: AvatarFacialHairColor; - facialHairType: AvatarFacialHairType; - accessoriesType: AvatarAccessoriesType; - eyeType: AvatarEyeType; - eyebrowType: AvatarEyebrowType; - mouthType: AvatarMouthType; -} +} from "constants/avatar"; +import {AvataaarProps} from "types/avatar"; +import {getColorClassName, getColorForIndex} from "../../constants/colors"; export type AvatarProps = { - seed: string; + seed?: string; className?: string; avatar?: AvataaarProps; }; @@ -104,6 +77,9 @@ export const generateRandomProps = (seed: string) => { */ export const Avatar = React.memo( ({className, seed, avatar}: AvatarProps) => { + if (!seed) { + return null; + } if (!avatar) { const {accentColorClass, ...avatarProps} = generateRandomProps(seed); return ; diff --git a/src/components/BoardHeader/BoardHeader.tsx b/src/components/BoardHeader/BoardHeader.tsx index f6e40f8d38..e511ee48b6 100644 --- a/src/components/BoardHeader/BoardHeader.tsx +++ b/src/components/BoardHeader/BoardHeader.tsx @@ -1,16 +1,16 @@ import {useState, VFC} from "react"; import {LockClosed, Open as Globe, KeyProtected, Share} from "components/Icon"; import {BoardUsers} from "components/BoardUsers"; -import store, {useAppSelector} from "store"; +import {useAppDispatch, useAppSelector} from "store"; import {ScrumlrLogo} from "components/ScrumlrLogo"; import {HeaderMenu} from "components/BoardHeader/HeaderMenu"; import {useTranslation} from "react-i18next"; -import {Actions} from "store/action"; import {ConfirmationDialog} from "components/ConfirmationDialog"; import {shallowEqual} from "react-redux"; import "./BoardHeader.scss"; import {ShareButton} from "components/ShareButton"; import {Tooltip} from "react-tooltip"; +import {leaveBoard} from "store/features"; import {DEFAULT_BOARD_NAME} from "../../constants/misc"; export interface BoardHeaderProps { @@ -18,6 +18,7 @@ export interface BoardHeaderProps { } export const BoardHeader: VFC = (props) => { + const dispatch = useAppDispatch(); const {t} = useTranslation(); const state = useAppSelector( (rootState) => ({ @@ -36,7 +37,7 @@ export const BoardHeader: VFC = (props) => { { - store.dispatch(Actions.leaveBoard()); + dispatch(leaveBoard()); window.location.pathname = "/"; }} onDecline={() => setShowConfirmationDialog(false)} @@ -69,7 +70,9 @@ export const BoardHeader: VFC = (props) => { {t(`AccessPolicy.${state.accessPolicy}`)}
-

{state.name || DEFAULT_BOARD_NAME}

+

+ {state.name || DEFAULT_BOARD_NAME} +

> = const Icon = icon!; return ( - + + + {colorsWithoutSelectedColor.map((color) => { + const anchor = uniqueId(`color-picker-${color.toString()}`); + return ( +
  • + +
  • + ); + })} + + + ); +}; diff --git a/src/components/Column/Column.scss b/src/components/Column/Column.scss index 45573ee433..007e9c08ee 100644 --- a/src/components/Column/Column.scss +++ b/src/components/Column/Column.scss @@ -52,6 +52,8 @@ display: flex; flex-direction: column; padding: 0 $spacing--xl; + + z-index: $column-header-z-index; } .column__header-title { diff --git a/src/components/Column/Column.tsx b/src/components/Column/Column.tsx index 025bb5db04..f9ec55ef00 100644 --- a/src/components/Column/Column.tsx +++ b/src/components/Column/Column.tsx @@ -1,22 +1,22 @@ -import "./Column.scss"; -import {Color, getColorClassName} from "constants/colors"; -import {NoteInput} from "components/NoteInput"; +import {useTranslation} from "react-i18next"; +import _ from "underscore"; import {useEffect, useRef, useState} from "react"; import classNames from "classnames"; import {Tooltip} from "react-tooltip"; -import {useAppSelector} from "store"; -import {Actions} from "store/action"; -import {Close, MarkAsDone, Hidden, ThreeDots} from "components/Icon"; -import _ from "underscore"; -import {useDispatch} from "react-redux"; -import {useTranslation} from "react-i18next"; +import {useAppDispatch, useAppSelector} from "store"; +import {createColumn, deleteColumnOptimistically, editColumn, editColumnOptimistically} from "store/features"; +import {Color, getColorClassName} from "constants/colors"; import {hotkeyMap} from "constants/hotkeys"; +import {NoteInput} from "components/NoteInput"; import {Droppable} from "components/DragAndDrop/Droppable"; -import {useStripeOffset} from "utils/hooks/useStripeOffset"; import {EmojiSuggestions} from "components/EmojiSuggestions"; +import {ColumnSettings} from "components/Column/ColumnSettings"; +import {Close, MarkAsDone, Hidden, ThreeDots} from "components/Icon"; +import {Note} from "components/Note"; import {useEmojiAutocomplete} from "utils/hooks/useEmojiAutocomplete"; -import {Note} from "../Note"; -import {ColumnSettings} from "./ColumnSettings"; +import {useStripeOffset} from "utils/hooks/useStripeOffset"; +import {useTextOverflow} from "utils/hooks/useTextOverflow"; +import "./Column.scss"; const {SELECT_NOTE_INPUT_FIRST_KEY} = hotkeyMap; @@ -30,7 +30,9 @@ export interface ColumnProps { export const Column = ({id, name, color, visible, index}: ColumnProps) => { const {t} = useTranslation(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); + + const {isTextTruncated, textRef} = useTextOverflow(name); const notes = useAppSelector( (state) => @@ -46,7 +48,7 @@ export const Column = ({id, name, color, visible, index}: ColumnProps) => { const viewer = useAppSelector((state) => state.participants!.self); const colorClassName = getColorClassName(color); - const isModerator = viewer.role === "OWNER" || viewer.role === "MODERATOR"; + const isModerator = viewer?.role === "OWNER" || viewer?.role === "MODERATOR"; const {value: columnName, ...emoji} = useEmojiAutocomplete({maxInputLength: 32, initialValue: name}); const [columnNameMode, setColumnNameMode] = useState<"VIEW" | "EDIT">("VIEW"); @@ -58,7 +60,17 @@ export const Column = ({id, name, color, visible, index}: ColumnProps) => { const closeButtonRef = useRef(null); const toggleVisibilityHandler = () => { - dispatch(Actions.editColumn(id, {name, color, index, visible: !visible})); + dispatch( + editColumn({ + id, + column: { + name, + color, + index, + visible: !visible, + }, + }) + ); }; const [localNotes, setLocalNotes] = useState(notes); @@ -80,14 +92,34 @@ export const Column = ({id, name, color, visible, index}: ColumnProps) => { const handleEditColumnName = (newName: string) => { if (isTemporary) { if (!newName) { - dispatch(Actions.deleteColumnOptimistically(id)); + dispatch(deleteColumnOptimistically(id)); } else { - dispatch(Actions.editColumnOptimistically(id, {name: newName, color, visible, index})); // Prevents flicker when submitting a new column - dispatch(Actions.createColumn({name: newName, color, visible, index})); + dispatch( + editColumnOptimistically({ + id, + column: { + name: newName, + color, + visible, + index, + }, + }) + ); // Prevents flicker when submitting a new column + dispatch(createColumn({name: newName, color, visible, index})); setIsTemporary(false); } } else { - dispatch(Actions.editColumn(id, {name: newName, color, visible, index})); + dispatch( + editColumn({ + id, + column: { + name: newName, + color, + visible, + index, + }, + }) + ); } setColumnNameMode("VIEW"); }; @@ -97,6 +129,8 @@ export const Column = ({id, name, color, visible, index}: ColumnProps) => {
    {!visible && }

    { if (isModerator) { @@ -107,14 +141,17 @@ export const Column = ({id, name, color, visible, index}: ColumnProps) => { > {name}

    - - {name} - + {isTextTruncated && ( + + {name} + + )}
    ) : ( <> { @@ -123,7 +160,7 @@ export const Column = ({id, name, color, visible, index}: ColumnProps) => { if (e.key === "Escape") { if (isTemporary) { - dispatch(Actions.deleteColumnOptimistically(id)); + dispatch(deleteColumnOptimistically(id)); } setColumnNameMode("VIEW"); } else if (e.key === "Enter") { @@ -164,7 +201,7 @@ export const Column = ({id, name, color, visible, index}: ColumnProps) => { ref={closeButtonRef} onClick={() => { if (isTemporary) { - dispatch(Actions.deleteColumnOptimistically(id)); + dispatch(deleteColumnOptimistically(id)); } setColumnNameMode("VIEW"); }} @@ -173,9 +210,9 @@ export const Column = ({id, name, color, visible, index}: ColumnProps) => { )} - {!isTemporary && ( + {!isTemporary && !openedColumnSettings && ( )} @@ -212,17 +249,9 @@ export const Column = ({id, name, color, visible, index}: ColumnProps) => { {notes.length} )} - {isModerator && renderColumnModifiers()} + {!openedColumnSettings && isModerator && renderColumnModifiers()} {openedColumnSettings && ( - setOpenedColumnSettings(false)} - onNameEdit={() => setColumnNameMode("EDIT")} - /> + setOpenedColumnSettings(false)} onNameEdit={() => setColumnNameMode("EDIT")} /> )} ul { - list-style: none; - padding: $spacing--xs 0; - margin: 0; -} - -.column__header-menu-dropdown > ul > li { - height: 32px; - padding: 0 $spacing--sm; - background-color: $gray--000; - - &:hover { - filter: $darken--slightly; - } - - &:focus-within { - background-color: $gray--200; - } -} - -.column__header-menu-dropdown > ul > li > button { - cursor: pointer; - border: 0; - outline: none; - background-color: transparent; - padding: 0; - margin: 0; - height: 100%; - width: 100%; - display: flex; - align-items: center; - gap: 10px; - font-size: $text-size--small; -} - -.column__header-menu-dropdown > ul > li > button > svg { - height: $icon--large; - width: $icon--large; -} - -.column__header-menu-dropdown > ul > li:last-of-type { - height: auto; - padding: $spacing--xs $spacing--base; - display: flex; - justify-content: space-between; -} - -.column__header-menu-dropdown > ul > li:last-child > button { - height: 18px; - width: 18px; - border-radius: 4px; - background-color: var(--accent-color--light); - transition: all 0.08s ease-out; - - &:hover { - transform: scale(1.1); - } - - &:focus-visible { - box-shadow: 0 0 0 2px $navy--300; - } -} - -// Hacky way to not show delete column button if it's the last column -.column:only-of-type .column__header-menu-dropdown > ul > li:nth-child(5) { - display: none; -} - -[theme="dark"] { - .column__header-menu-dropdown { - background-color: $navy--500; - } - - .column__header-menu-dropdown > ul > li { - background-color: $navy--500; - - &:hover { - filter: $brighten--slightly; - } - - &:focus-within { - background-color: $navy--400; - } - } - - .column__header-menu-dropdown > ul > li > button { - color: $gray--000; - } - - .column__header-menu-dropdown > ul > li:last-child > button:focus-visible { - box-shadow: 0 0 0 2px $gray--000; - } +.column-settings { + z-index: $column-header-z-index; // display over rest of column } diff --git a/src/components/Column/ColumnSettings.tsx b/src/components/Column/ColumnSettings.tsx index b4dfad2795..a20ba49d57 100644 --- a/src/components/Column/ColumnSettings.tsx +++ b/src/components/Column/ColumnSettings.tsx @@ -1,144 +1,136 @@ -import {FC} from "react"; -import {Actions} from "store/action"; -import {Hidden, Visible, Edit, ArrowLeft, ArrowRight, Trash} from "components/Icon"; -import {Color, getColorClassName, getColorForIndex} from "constants/colors"; +import {useEffect, useState} from "react"; import {useTranslation} from "react-i18next"; -import {useDispatch} from "react-redux"; -import "./ColumnSettings.scss"; -import {useAppSelector} from "store"; -import classNames from "classnames"; +import {useAppDispatch, useAppSelector} from "store"; +import {Column, createColumnOptimistically, deleteColumn, editColumn, setShowHiddenColumns} from "store/features"; +import {Color, getColorForIndex, COLOR_ORDER} from "constants/colors"; +import {TEMPORARY_COLUMN_ID, TOAST_TIMER_SHORT} from "constants/misc"; import {useOnBlur} from "utils/hooks/useOnBlur"; -import {Toast} from "../../utils/Toast"; -import {TEMPORARY_COLUMN_ID, TOAST_TIMER_SHORT} from "../../constants/misc"; +import {Toast} from "utils/Toast"; +import {Hidden, Visible, Edit, ArrowLeft, ArrowRight, Trash, Close} from "components/Icon"; +import {MiniMenu, MiniMenuItem} from "components/MiniMenu/MiniMenu"; +import {ColorPicker} from "components/ColorPicker/ColorPicker"; +import "./ColumnSettings.scss"; type ColumnSettingsProps = { - id: string; - name: string; - color: Color; - visible: boolean; - index: number; - onClose?: () => void; - onNameEdit?: () => void; + column: Column; + onClose: () => void; + onNameEdit: () => void; }; -export const ColumnSettings: FC = ({id, name, color, visible, index, onClose, onNameEdit}) => { +export const ColumnSettings = (props: ColumnSettingsProps) => { const {t} = useTranslation(); - const showHiddenColumns = useAppSelector((state) => state.participants?.self.showHiddenColumns); - const dispatch = useDispatch(); - const columnSettingsRef = useOnBlur(onClose ?? (() => {})); + const showHiddenColumns = useAppSelector((state) => state.participants?.self!.showHiddenColumns); + const dispatch = useAppDispatch(); + const columnSettingsRef = useOnBlur(props.onClose); + const [openedColorPicker, setOpenedColorPicker] = useState(false); const handleAddColumn = (columnIndex: number) => { if (!showHiddenColumns) { - dispatch(Actions.setShowHiddenColumns(true)); + dispatch(setShowHiddenColumns({showHiddenColumns: true})); Toast.success({title: t("Toast.hiddenColumnsVisible"), autoClose: TOAST_TIMER_SHORT}); } - const randomColor = getColorForIndex(Math.floor(Math.random() * 100)); - dispatch(Actions.createColumnOptimistically({id: TEMPORARY_COLUMN_ID, name: "", color: randomColor, visible: false, index: columnIndex})); + const randomColor = getColorForIndex(Math.floor(Math.random() * COLOR_ORDER.length)); + dispatch(createColumnOptimistically({id: TEMPORARY_COLUMN_ID, name: "", color: randomColor, visible: false, index: columnIndex})); }; + useEffect(() => { + const handleKeyPress = (e: KeyboardEvent) => { + if (e.key === "Escape" && !openedColorPicker) { + props.onClose(); + } + }; + + document.addEventListener("keydown", handleKeyPress, false); // trigger in bubble phase + return () => document.removeEventListener("keydown", handleKeyPress, false); + }, [openedColorPicker, props]); + + const onSelectColor = (color: Color) => { + props.onClose(); + dispatch( + editColumn({ + id: props.column.id, + column: { + ...props.column, + color, // overwrite + }, + }) + ); + }; + + const menuItems: MiniMenuItem[] = [ + { + label: t("Column.deleteColumn"), + element: , + onClick: () => { + props.onClose(); + dispatch(deleteColumn(props.column.id)); + }, + }, + { + label: t("Column.color"), + element: ( + setOpenedColorPicker(false)} + /> + ), + onClick: () => setOpenedColorPicker((o) => !o), + }, + { + label: t("Column.addColumnLeft"), + element: , + onClick: () => { + props.onClose(); + handleAddColumn(props.column.index); + }, + }, + { + label: t("Column.addColumnRight"), + element: , + onClick: () => { + props.onClose(); + handleAddColumn(props.column.index + 1); + }, + }, + { + label: props.column.visible ? t("Column.hideColumn") : t("Column.showColumn"), + element: props.column.visible ? : , + onClick: () => { + props.onClose?.(); + dispatch( + editColumn({ + id: props.column.id, + column: { + name: props.column.name, + color: props.column.color, + index: props.column.index, + visible: !props.column.visible, + }, + }) + ); + }, + }, + { + label: t("Column.editName"), + element: , + onClick: () => { + props.onNameEdit(); + props.onClose(); + }, + }, + { + label: t("Column.resetName"), + element: , + onClick: props.onClose, + }, + ]; + return ( -
    -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • -
    • -
    +
    +
    ); }; diff --git a/src/components/Column/__tests__/Column.test.tsx b/src/components/Column/__tests__/Column.test.tsx index e40277f553..c8fc2910bb 100644 --- a/src/components/Column/__tests__/Column.test.tsx +++ b/src/components/Column/__tests__/Column.test.tsx @@ -2,7 +2,7 @@ import {Column} from "components/Column"; import {render} from "testUtils"; import {Provider} from "react-redux"; import getTestStore from "utils/test/getTestStore"; -import {ApplicationState} from "types"; +import {ApplicationState} from "store"; import {CustomDndContext} from "components/DragAndDrop/CustomDndContext"; jest.mock("utils/hooks/useImageChecker.ts", () => ({ diff --git a/src/components/Column/__tests__/__snapshots__/Column.test.tsx.snap b/src/components/Column/__tests__/__snapshots__/Column.test.tsx.snap index eec4484e63..2d1d707a0e 100644 --- a/src/components/Column/__tests__/__snapshots__/Column.test.tsx.snap +++ b/src/components/Column/__tests__/__snapshots__/Column.test.tsx.snap @@ -25,6 +25,7 @@ exports[`Column should have correct style show column with correct style 1`] = `

    Testheader 1 @@ -42,9 +43,11 @@ exports[`Column should have correct style show column with correct style 1`] = ` > three-dots.svg +