diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 2a55263bd..8b1353d2a 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -33,13 +33,23 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 + - name: Cache Docker layers + uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + - name: BUILD uses: docker/build-push-action@v2 with: push: false load: true tags: squidex-tmp - build-args: "SQUIDEX__VERSION=4.0.0-dev-${{ env.BUILD_NUMBER }}" + build-args: "SQUIDEX__VERSION=5.0.0-dev-${{ env.BUILD_NUMBER }}" + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new - name: Export Image run: docker save squidex-tmp | gzip > squidex-tmp.tar.gz @@ -50,6 +60,11 @@ jobs: path: squidex-tmp.tar.gz key: squidex-dev-image-${{ github.sha }} + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache + test: needs: build runs-on: ubuntu-latest @@ -95,6 +110,13 @@ jobs: options: --name test volumes: ${{ github.workspace }}:/src run: dotnet test /src/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj --filter Category!=NotAutomated + + - name: Dump docker logs on failure + if: failure() + uses: jwalton/gh-docker-logs@v1 + with: + images: 'squidex-tmp' + tail: '100' - name: Cleanup Test if: always() diff --git a/.github/workflows/marketplace-aws.yml b/.github/workflows/marketplace-aws.yml index 58f1b97fb..338f400dd 100644 --- a/.github/workflows/marketplace-aws.yml +++ b/.github/workflows/marketplace-aws.yml @@ -17,8 +17,7 @@ jobs: with: command: validate arguments: -syntax-only - target: aws.pkr.hcl - working-directory: packer/ + target: packer/aws.pkr.hcl publish: needs: validate @@ -30,15 +29,22 @@ jobs: - name: Inject slug/short variables uses: rlespinasse/github-slug-action@v3.x + - name: Init + uses: hashicorp/packer-github-actions@master + with: + command: init + target: packer/aws.pkr.hcl + env: + PACKER_LOG: 1 + - name: Build uses: hashicorp/packer-github-actions@master with: command: build arguments: "-color=false -on-error=abort" - target: aws.pkr.hcl + target: packer/aws.pkr.hcl env: PACKER_LOG: 1 PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}" AWS_ACCESS_KEY_ID: "${{ secrets.AWS_ACCESS_KEY_ID }}" AWS_SECRET_ACCESS_KEY: "${{ secrets.AWS_SECRET_ACCESS_KEY }}" - working-directory: packer/ diff --git a/.github/workflows/marketplace-azure.yml b/.github/workflows/marketplace-azure.yml index 885157103..1cefcc5c4 100644 --- a/.github/workflows/marketplace-azure.yml +++ b/.github/workflows/marketplace-azure.yml @@ -5,6 +5,8 @@ on: release: types: [ released ] +# az ad sp create-for-rbac --name squidex + jobs: validate: runs-on: ubuntu-latest @@ -13,13 +15,11 @@ jobs: uses: actions/checkout@v2 - name: Validate Template - if: false uses: hashicorp/packer-github-actions@master with: command: validate arguments: -syntax-only - target: azure.pkr.hcl - working-directory: packer/ + target: packer/azure.pkr.hcl publish: needs: validate @@ -31,14 +31,24 @@ jobs: - name: Inject slug/short variables uses: rlespinasse/github-slug-action@v3.x + - name: Init + uses: hashicorp/packer-github-actions@master + with: + command: init + target: packer/azure.pkr.hcl + env: + PACKER_LOG: 1 + - name: Build - if: false uses: hashicorp/packer-github-actions@master with: command: build arguments: "-color=false -on-error=abort" - target: azure.pkr.hcl + target: packer/azure.pkr.hcl env: PACKER_LOG: 1 PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}" - working-directory: packer/ + PKR_VAR_subscription_id: "${{ secrets.AZURE_SUBSCRIPTION_ID }}" + PKR_VAR_tenant_id: "${{ secrets.AZURE_TENANT_ID }}" + PKR_VAR_client_id: "${{ secrets.AZURE_CLIENT_ID }}" + PKR_VAR_client_secret: "${{ secrets.AZURE_CLIENT_SECRET }}" diff --git a/.github/workflows/marketplace-digitalocean.yml b/.github/workflows/marketplace-digitalocean.yml index 70f59e9f5..39ee072cc 100644 --- a/.github/workflows/marketplace-digitalocean.yml +++ b/.github/workflows/marketplace-digitalocean.yml @@ -17,8 +17,7 @@ jobs: with: command: validate arguments: -syntax-only - target: digitalocean.pkr.hcl - working-directory: packer/ + target: packer/digitalocean.pkr.hcl publish: needs: validate @@ -30,14 +29,21 @@ jobs: - name: Inject slug/short variables uses: rlespinasse/github-slug-action@v3.x + - name: Init + uses: hashicorp/packer-github-actions@master + with: + command: init + target: packer/digitalocean.pkr.hcl + env: + PACKER_LOG: 1 + - name: Build uses: hashicorp/packer-github-actions@master with: command: build arguments: "-color=false -on-error=abort" - target: digitalocean.pkr.hcl + target: packer/digitalocean.pkr.hcl env: PACKER_LOG: 1 PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}" DIGITALOCEAN_API_TOKEN: "${{ secrets.DIGITALOCEAN_API_TOKEN }}" - working-directory: packer/ diff --git a/.github/workflows/marketplace-gcp.yml b/.github/workflows/marketplace-gcp.yml index 1cc8e177c..43fac4078 100644 --- a/.github/workflows/marketplace-gcp.yml +++ b/.github/workflows/marketplace-gcp.yml @@ -13,13 +13,11 @@ jobs: uses: actions/checkout@v2 - name: Validate Template - if: false uses: hashicorp/packer-github-actions@master with: command: validate arguments: -syntax-only - target: gcp.pkr.hcl - working-directory: packer/ + target: packer/gcp.pkr.hcl publish: needs: validate @@ -31,14 +29,24 @@ jobs: - name: Inject slug/short variables uses: rlespinasse/github-slug-action@v3.x + - name: Extract service account + run: echo "${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}" | base64 -d > /tmp/squidex.json + + - name: Init + uses: hashicorp/packer-github-actions@master + with: + command: init + target: packer/gcp.pkr.hcl + env: + PACKER_LOG: 1 + - name: Build - if: false uses: hashicorp/packer-github-actions@master with: command: build arguments: "-color=false -on-error=abort" - target: gcp.pkr.hcl + target: packer/gcp.pkr.hcl env: PACKER_LOG: 1 PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}" - working-directory: packer/ + GOOGLE_APPLICATION_CREDENTIALS: "/tmp/squidex.json" diff --git a/.github/workflows/marketplace-heroku.yml b/.github/workflows/marketplace-heroku.yml deleted file mode 100644 index 23b38ed17..000000000 --- a/.github/workflows/marketplace-heroku.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Marketplace - Heroku -concurrency: marketplace-heroku - -on: - release: - types: [ released ] - -jobs: - validate: - runs-on: ubuntu-latest - steps: - - name: Checkout Repository - uses: actions/checkout@v2 - - - name: Validate Template - if: false - uses: hashicorp/packer-github-actions@master - with: - command: validate - arguments: -syntax-only - target: heroku.pkr.hcl - working-directory: packer/ - - publish: - needs: validate - runs-on: ubuntu-latest - steps: - - name: Checkout Repository - uses: actions/checkout@v2 - - - name: Inject slug/short variables - uses: rlespinasse/github-slug-action@v3.x - - - name: Build - if: false - uses: hashicorp/packer-github-actions@master - with: - command: build - arguments: "-color=false -on-error=abort" - target: heroku.pkr.hcl - env: - PACKER_LOG: 1 - PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}" - working-directory: packer/ diff --git a/.github/workflows/marketplace-render.yml b/.github/workflows/marketplace-render.yml deleted file mode 100644 index b5d6478a9..000000000 --- a/.github/workflows/marketplace-render.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Marketplace - Render -concurrency: marketplace-render - -on: - release: - types: [ released ] - -jobs: - validate: - runs-on: ubuntu-latest - steps: - - name: Checkout Repository - uses: actions/checkout@v2 - - - name: Validate Template - if: false - uses: hashicorp/packer-github-actions@master - with: - command: validate - arguments: -syntax-only - target: render.pkr.hcl - working-directory: packer/ - - publish: - needs: validate - runs-on: ubuntu-latest - steps: - - name: Checkout Repository - uses: actions/checkout@v2 - - - name: Inject slug/short variables - uses: rlespinasse/github-slug-action@v3.x - - - name: Build - if: false - uses: hashicorp/packer-github-actions@master - with: - command: build - arguments: "-color=false -on-error=abort" - target: render.pkr.hcl - env: - PACKER_LOG: 1 - PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}" - working-directory: packer/ diff --git a/.github/workflows/marketplace-vultr.yml b/.github/workflows/marketplace-vultr.yml index a18f18cbf..13be51819 100644 --- a/.github/workflows/marketplace-vultr.yml +++ b/.github/workflows/marketplace-vultr.yml @@ -13,13 +13,11 @@ jobs: uses: actions/checkout@v2 - name: Validate Template - if: false uses: hashicorp/packer-github-actions@master with: command: validate arguments: -syntax-only - target: vultr.pkr.hcl - working-directory: packer/ + target: packer/vultr.pkr.hcl publish: needs: validate @@ -31,14 +29,21 @@ jobs: - name: Inject slug/short variables uses: rlespinasse/github-slug-action@v3.x + - name: Init + uses: hashicorp/packer-github-actions@master + with: + command: init + target: packer/vultr.pkr.hcl + env: + PACKER_LOG: 1 + - name: Build - if: false uses: hashicorp/packer-github-actions@master with: command: build arguments: "-color=false -on-error=abort" - target: vultr.pkr.hcl + target: packer/vultr.pkr.hcl env: PACKER_LOG: 1 PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}" - working-directory: packer/ + VULTR_API_KEY: "${{ secrets.VULTR_API_KEY }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index db727a9a1..573b0e016 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,6 +22,14 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 + - name: Cache Docker layers + uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + - name: BUILD uses: docker/build-push-action@v2 with: @@ -29,6 +37,8 @@ jobs: load: true tags: squidex-tmp build-args: "SQUIDEX__VERSION=${{ env.GITHUB_REF_SLUG }}" + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new - name: Export Image run: docker save squidex-tmp | gzip > squidex-tmp.tar.gz @@ -39,6 +49,11 @@ jobs: path: squidex-tmp.tar.gz key: squidex-release-image-${{ github.sha }} + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache + test: needs: build runs-on: ubuntu-latest @@ -144,23 +159,23 @@ jobs: - name: Load Image run: docker load < squidex-tmp.tar.gz - - name: Get Binaries - run: | - mkdir /build 2> /dev/null - docker create --name squidex-container squidex-tmp - docker cp squidex-container:/app/. /build + - name: Make directories + run: sudo mkdir /build /release + + - name: Create container + run: docker create --name squidex-container squidex-tmp:latest + + - name: Get binaries + run: sudo docker cp squidex-container:/app/. /build - name: ZIP Binaries - run: | - mkdir /release 2> /dev/null - zip -r /release/binaries.zip . + run: sudo zip -r /release/binaries.zip . working-directory: /build - name: Get Changelog Entry id: changelog_reader uses: mindsers/changelog-reader-action@v2 with: - validation_depth: 10 version: ${{ env.GITHUB_REF_SLUG }} path: ./CHANGELOG.md @@ -169,10 +184,10 @@ jobs: with: allowUpdates: true artifactErrorsFailBuild: true - artifact: "/release/binaries.zip" + artifacts: "/release/binaries.zip" body: ${{ steps.changelog_reader.outputs.changes }} name: ${{ env.GITHUB_REF_SLUG }} - replaceArtifacts: true + replacesArtifacts: true token: ${{ secrets.GITHUB_TOKEN }} - name: Cleanup Binaries diff --git a/CHANGELOG.md b/CHANGELOG.md index a0a26e9cd..8d2b10976 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [5.8.0] - 2021-06-28 + +### Fixed + +* **API**: Fixes for anonymous write access. +* **API**: Fixes for client access when creating apps. +* **Schemas**: Correct sync of field rules, especially for CLI. +* **UserManagement**: Fix pagination +* **UI**: Encode IDs to allow custom content IDs with slash. +* **UI**: Fixes typos for italian translation. +* **UI**: Allow scrolling when content is disabled. +* **UI**: Fixes references/referencing view for localized content. +* **UI**: Fix confirm click. + +### Added + +* **API**: Better timeout and cancellation handling. +* **API**: Default timeouts for most important MongoDB calls. +* **API**: Better API tests to improve stability. +* **Assets**: Additional configuration flag to allow one folder per asset. +* **Contents**: Read published contents from secondary MongodB instances for better load distribution. +* **Contents**: Better indexes for improved performance. +* **Rules**: New liquid and javascript extensions to read the asset as text in rules. +* **Rules**: Simpler syntax to resolve assets and contents in liquid templates. +* **Contents**: Array builder when building custom code extension. +* **UI**: Show SVG as images when in contnet overview. +* **UI**: Chinese translation. + ## [5.7.1] - 2021-05-21 ### Fixed diff --git a/Dockerfile b/Dockerfile index 84704e0c2..25d25c73d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # FROM mcr.microsoft.com/dotnet/sdk:5.0 as backend -ARG SQUIDEX__VERSION=4.0.0 +ARG SQUIDEX__VERSION=5.0.0 WORKDIR /src diff --git a/README.md b/README.md index 427a5917a..5e2e2ebc2 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,24 @@ Current Version ![GitHub release](https://img.shields.io/github/release/squidex/ * [MongoDB](https://www.mongodb.com/) * [.NET Core SDK](https://www.microsoft.com/net/download/core#/current) (Already part of Visual Studio 2017) +## Deployment Options + +| Platform | Documentation | Quicklink | +| -------- | ------------- | ---- | +| Azure | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-azure) | +| AWS | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-aws) | +| Docker | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-docker) | [![Docker Compose](https://img.shields.io/badge/-docker--compose.yml-2496ED?style=for-the-badge&logo=docker&logoColor=ffffff)](https://github.com/Squidex/squidex-hosting/blob/master/docker-compose/docker-compose.yml) | +| GCP | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-gcp) | +| Heroku | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-heroku) | [![Deploy to Heroku](https://img.shields.io/badge/-Deploy%20to%20Heroku-430098?style=for-the-badge&logo=heroku&logoColor=ffffff)](https://heroku.com/deploy?template=https://github.com/Squidex/squidex) | +| IIS | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-azure) | +| k8 | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-kubernetes) | +| Render | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-render) | [![Deploy to Render](https://img.shields.io/badge/-Deploy%20to%20Render-44E4B4?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAADyklEQVR4Xu1aT0gUURh/k5qV2y5pEbpqHiqqy4IIQUGghbNOK53WkwQFdekqRHXNDkHXIKhuXdZTZu6MRkbkJRFJKAKNUnQPEir+IXdr98XbdXbfzrznjLvvObM7s5dld7553/f93u/78/4IwOEfweH+AxcAlwEOR8ANAYcTwE2CfEIAAqEzpjSmQPLobhj2zi9N4fLiotxKet87vvZloKcnqT6jyW2sx2fGz1xb38kGJgCEv0b2h/yBvuu+0/0kZRAAAtX0/wqCkGcPhBAJ6T7SbNQXPSWtqQ9ocsFFpUNpDI5xBeBnYn2+pcrTtJuZpsnSANBCRQNAK8cVgM7F4W6lvmuQZRYxy4De2ajvlSkGvO1QGkPsGXB/aeJF/7G2myxmHR/DGIBMREizsnUhcO/3xMtHdW03WDuPxjMGIKPVshwgLshXZb84lO88OcUVApDtAYAwBUn5XHV2Kbn1tzc2FhptlkYKAcCKd0yXwbnExnxzVQ012z9emb5ztzbw1AonitFpCgBU5yPnwnGaouCCcl5pCn4uxhCr3jUFwLPVbw9v+84+0BkJAXiyMn2rry7w3CoHkN7u2JtDJP0HPv2J4x0jScYUALROaym5lTheebDaSueRbu6dIE2BOBe9PNIivS9vACAQIIApIn00vbtVQOgnKFOambTCV2KDzaP13XOlBUDGWiYAoKVmtEGcJCULbeNiHwYwBkBuECdLiwEMQwAxwO4AFMM8wzLoAuAyYDsECIs+uyRBx4dACkJIimVmZTAvCWJMsAsDuLbCpZAE9xYACADiG6KcAxmQ4b8aBQ4EIJNvHQ+AWnbswgDuZdDuiyHuAOTKYH435BgGuIuhklsO89wP2I4C9LXPjltiWJTyaYWxjGOXHMC3E/wlt8onSm1HiGcIZNsgG7XCKZjeu9aeWzoqBEhn1EwACMNIxcaypyZNqmUAQG0uCUTrcvd0imlGin23a2bYSxrDc7Jrc0AQspepCj4aK9ZAO79vuClqZ+NZ2OYCYISi2R0hdEQ9WB/aNHOAQqvb1Csyaobb/t7TO0Kirg/I5VvcYBUAUjY2e/fHSC49NgRA+kG5KIkrhwAEYwwuSuIM0DpHAqAQBtA2WEzfFE33AXrNTMpgFgCDcwGuIaDxbW9DwOTJkAsAzyRoJQMufn992OOpbsvZ8A8AUJn+id/EDkciFWsXvJdwW5EUktbe2BYX5HZSrtDLDbWrunB5r1/8iHd4tPHiNdVTH460r+5U6dw+wKgPKPfnLgPKfYaN/HMZYIRQuT93GVDuM2zk338nAalfI74e1QAAAABJRU5ErkJggg==&logoColor=ffffff)](https://render.com/deploy?repo=https://github.com/Squidex/squidex) | +| Vultr | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-vultr) | [![Deploy to Vultr](https://img.shields.io/badge/-Deploy%20to%20Vultr-007BFC?style=for-the-badge&logo=vultr&logoColor=ffffff)](https://www.vultr.com/marketplace/apps/squidex) | + + + + + ## Contributors ### Core Team and Founders diff --git a/app.json b/app.json new file mode 100644 index 000000000..6e243b59a --- /dev/null +++ b/app.json @@ -0,0 +1,35 @@ +{ + "name": "Squidex", + "description": "Headless CMS and Content Managment Hub", + "website": "https://squidex.io/", + "repository": "https://github.com/Squidex/squidex", + "logo": "https://avatars.githubusercontent.com/u/25371797?s=200&v=4", + "success_url": "/", + "env": { + "DOMAIN": { + "description": "The domain name under which your instance is available", + "value": "https://[YOUR-HEROKU-APPNAME].herokuapp.com" + }, + "MONGO_USERNAME": { + "description": "Mongo Username, follow https://devcenter.heroku.com/articles/ormongo#open-the-dashboard to create a database named Squidex and then edit the deployment to reflect the credentials you used", + "value": "Squidex" + }, + "MONGO_PASSWORD": { + "description": "Mongo Password (see MONGO_USERNAME)", + "value": "Squidex123" + } + }, + "formation": { + "web": { + "quantity": 1, + "size": "standard-1x" + } + }, + "addons": [ + { + "plan": "ormongo:2-mmap", + "as": "MONGO" + } + ], + "stack": "container" +} diff --git a/backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs index 7940c75e5..c0efcc7bc 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs @@ -23,8 +23,6 @@ namespace Squidex.Extensions.Actions.Comment public CommentActionHandler(RuleEventFormatter formatter, ICommandBus commandBus) : base(formatter) { - Guard.NotNull(commandBus, nameof(commandBus)); - this.commandBus = commandBus; } diff --git a/backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs index c53bb0fe7..1a5996b8e 100644 --- a/backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs @@ -30,10 +30,6 @@ namespace Squidex.Extensions.Actions.CreateContent public CreateContentActionHandler(RuleEventFormatter formatter, IAppProvider appProvider, ICommandBus commandBus, IJsonSerializer jsonSerializer) : base(formatter) { - Guard.NotNull(appProvider, nameof(appProvider)); - Guard.NotNull(commandBus, nameof(commandBus)); - Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); - this.appProvider = appProvider; this.commandBus = commandBus; this.jsonSerializer = jsonSerializer; diff --git a/backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs index cceb64de4..a28425109 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs @@ -24,8 +24,6 @@ namespace Squidex.Extensions.Actions.Fastly public FastlyActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory) : base(formatter) { - Guard.NotNull(httpClientFactory, nameof(httpClientFactory)); - this.httpClientFactory = httpClientFactory; } diff --git a/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs index 7089cff5d..890d58239 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs @@ -27,9 +27,6 @@ namespace Squidex.Extensions.Actions.Notification public NotificationActionHandler(RuleEventFormatter formatter, ICommandBus commandBus, IUserResolver userResolver) : base(formatter) { - Guard.NotNull(commandBus, nameof(commandBus)); - Guard.NotNull(userResolver, nameof(userResolver)); - this.commandBus = commandBus; this.userResolver = userResolver; diff --git a/backend/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs index 303087488..36b55a3ec 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs @@ -12,7 +12,6 @@ using System.Threading; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; -using Squidex.Infrastructure; namespace Squidex.Extensions.Actions.Slack { @@ -25,8 +24,6 @@ namespace Squidex.Extensions.Actions.Slack public SlackActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory) : base(formatter) { - Guard.NotNull(httpClientFactory, nameof(httpClientFactory)); - this.httpClientFactory = httpClientFactory; } diff --git a/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs index 38f3f0844..54ba308c2 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs @@ -12,7 +12,6 @@ using CoreTweet; using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; -using Squidex.Infrastructure; namespace Squidex.Extensions.Actions.Twitter { @@ -25,8 +24,6 @@ namespace Squidex.Extensions.Actions.Twitter public TweetActionHandler(RuleEventFormatter formatter, IOptions twitterOptions) : base(formatter) { - Guard.NotNull(twitterOptions, nameof(twitterOptions)); - this.twitterOptions = twitterOptions.Value; } diff --git a/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs index 031a92efc..f69eea162 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs @@ -23,8 +23,6 @@ namespace Squidex.Extensions.Actions.Webhook public WebhookActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory) : base(formatter) { - Guard.NotNull(httpClientFactory, nameof(httpClientFactory)); - this.httpClientFactory = httpClientFactory; } diff --git a/backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj b/backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj index e36e1c739..c77f5307c 100644 --- a/backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj +++ b/backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj @@ -8,16 +8,16 @@ - + - + - - + + - + diff --git a/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidatorFactory.cs b/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidatorFactory.cs index 0ca427f3b..ad8d053a5 100644 --- a/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidatorFactory.cs +++ b/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidatorFactory.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Generic; using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Entities.Contents.Repositories; -using Squidex.Infrastructure; namespace Squidex.Extensions.Validation { @@ -20,8 +19,6 @@ namespace Squidex.Extensions.Validation public CompositeUniqueValidatorFactory(IContentRepository contentRepository) { - Guard.NotNull(contentRepository, nameof(contentRepository)); - this.contentRepository = contentRepository; } diff --git a/backend/i18n/clean.bat b/backend/i18n/clean.bat new file mode 100644 index 000000000..eb4f22c8d --- /dev/null +++ b/backend/i18n/clean.bat @@ -0,0 +1,5 @@ +cd translator\Squidex.Translator + +dotnet run translate clean-backend ..\..\..\.. +dotnet run translate clean-frontend ..\..\..\.. + diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json index a14b65bf0..359942ff1 100644 --- a/backend/i18n/frontend_en.json +++ b/backend/i18n/frontend_en.json @@ -336,10 +336,10 @@ "common.separateByLine": "Separate by line", "common.settings": "Settings", "common.sidebar": "Sidebar Extension", - "common.sidebarTour": "The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.", + "common.sidebarTour": "The sidebar navigation contains useful context specific links. Here you can view the history of how this schema has changed over time.", "common.skipped": "Skipped", "common.slug": "Slug", - "common.stars.max": "Must not have more more than 15 stars", + "common.stars.max": "Must not have more than 15 stars", "common.status": "Status", "common.statusChangeTo": "Change to", "common.submit": "Submit", @@ -416,7 +416,7 @@ "contents.loadContentFailed": "Failed to load content. Please reload.", "contents.loadDataFailed": "Failed to load data. Please reload.", "contents.loadFailed": "Failed to load contents. Please reload.", - "contents.loadVersionFailed": "Failed to version a new version. Please reload.", + "contents.loadVersionFailed": "Failed to load a new version. Please reload.", "contents.localizedFieldDescription": "The '{fieldName}' field of the content item (localized).", "contents.newStatusFieldDescription": "The new status of the content item.", "contents.noReference": "- No Reference -", @@ -761,9 +761,11 @@ "schemas.fieldTypes.assets.sizeMin": "Max Size", "schemas.fieldTypes.boolean.description": "Yes or no, true or false.", "schemas.fieldTypes.component.description": "Embed another schema to this content.", - "schemas.fieldTypes.components.description": "Embed another schemas to this content as array.", + "schemas.fieldTypes.components.description": "Embed other schemas to this content as array.", "schemas.fieldTypes.dateTime.defaultMode": "Default Mode", "schemas.fieldTypes.dateTime.description": "Events date, opening hours.", + "schemas.fieldTypes.dateTime.format": "Pattern", + "schemas.fieldTypes.dateTime.formatHint": "The pattern when shown in the UI, see: https://date-fns.org/v2.22.1/docs/format", "schemas.fieldTypes.dateTime.rangeMax": "Max Value", "schemas.fieldTypes.dateTime.rangeMin": "Min Value", "schemas.fieldTypes.geolocation.description": "Coordinates: latitude and longitude.", @@ -777,6 +779,7 @@ "schemas.fieldTypes.references.countMin": "Min Items", "schemas.fieldTypes.references.description": "Links to other content items.", "schemas.fieldTypes.references.mustBePublished": "References must be published", + "schemas.fieldTypes.references.resolve": "Resolve references", "schemas.fieldTypes.references.resolveHint": "Show the name of the referenced item in content list when MaxItems is set to 1.", "schemas.fieldTypes.string.characters": "Characters", "schemas.fieldTypes.string.charactersMax": "Max Characters", @@ -941,6 +944,7 @@ "validation.min": "{field|upper} must be greater or equal to '{min}'.", "validation.minlength": "{field|upper} must have at least {requiredlength} item(s).", "validation.minlengthstring": "{field|upper} must have at least {requiredlength} character(s).", + "validation.notAnValidSvg": "The SVG is malicious and contains script tags.", "validation.pattern": "{field|upper} does not match to the pattern.", "validation.patternmessage": "{message}", "validation.required": "{field|upper} is required.", diff --git a/backend/i18n/frontend_it.json b/backend/i18n/frontend_it.json index c133c893d..cdf9da7b7 100644 --- a/backend/i18n/frontend_it.json +++ b/backend/i18n/frontend_it.json @@ -761,9 +761,11 @@ "schemas.fieldTypes.assets.sizeMin": "Dimensione Max", "schemas.fieldTypes.boolean.description": "Si o no, vero o falso.", "schemas.fieldTypes.component.description": "Embed another schema to this content.", - "schemas.fieldTypes.components.description": "Embed another schemas to this content as array.", + "schemas.fieldTypes.components.description": "Embed other schemas to this content as array.", "schemas.fieldTypes.dateTime.defaultMode": "Modalità predefinita", "schemas.fieldTypes.dateTime.description": "Data degli eventi, orari di apertura.", + "schemas.fieldTypes.dateTime.format": "Pattern", + "schemas.fieldTypes.dateTime.formatHint": "The pattern when shown in the UI, see: https://date-fns.org/v2.22.1/docs/format", "schemas.fieldTypes.dateTime.rangeMax": "Valore Max", "schemas.fieldTypes.dateTime.rangeMin": "Valore Min", "schemas.fieldTypes.geolocation.description": "Coordinate: latitudine e longitudine.", @@ -777,6 +779,7 @@ "schemas.fieldTypes.references.countMin": "Numero Min Elementi", "schemas.fieldTypes.references.description": "Link ad altri elementi del contenuto.", "schemas.fieldTypes.references.mustBePublished": "I contenuti collegati devono essere pubblicati", + "schemas.fieldTypes.references.resolve": "Resolve references", "schemas.fieldTypes.references.resolveHint": "Mostra il nome dell'elemento collegato (riferimento) nella lista dei contenuti quando il numero massimo di elementi è impostato a 1.", "schemas.fieldTypes.string.characters": "Caratteri", "schemas.fieldTypes.string.charactersMax": "Max numero di Caratteri", @@ -941,6 +944,7 @@ "validation.min": "{field|upper} deve essere maggiore o uguale a '{min}'.", "validation.minlength": "{field|upper} deve avere almeno {requiredlength} elemento(i).", "validation.minlengthstring": "{field|upper} deve avere almeno {requiredlength} carattere(i).", + "validation.notAnValidSvg": "The SVG is malicious and contains script tags.", "validation.pattern": "{field|upper} non corrisponde al pattern.", "validation.patternmessage": "{message}", "validation.required": "{field|upper} è obbligatorio.", diff --git a/backend/i18n/frontend_nl.json b/backend/i18n/frontend_nl.json index b2a16b822..e6a78cec0 100644 --- a/backend/i18n/frontend_nl.json +++ b/backend/i18n/frontend_nl.json @@ -761,9 +761,11 @@ "schemas.fieldTypes.assets.sizeMin": "Max. grootte", "schemas.fieldTypes.boolean.description": "Ja of nee, waar of niet waar.", "schemas.fieldTypes.component.description": "Embed another schema to this content.", - "schemas.fieldTypes.components.description": "Embed another schemas to this content as array.", + "schemas.fieldTypes.components.description": "Embed other schemas to this content as array.", "schemas.fieldTypes.dateTime.defaultMode": "Standaardmodus", "schemas.fieldTypes.dateTime.description": "Datum van evenementen, openingstijden.", + "schemas.fieldTypes.dateTime.format": "Pattern", + "schemas.fieldTypes.dateTime.formatHint": "The pattern when shown in the UI, see: https://date-fns.org/v2.22.1/docs/format", "schemas.fieldTypes.dateTime.rangeMax": "Max. waarde", "schemas.fieldTypes.dateTime.rangeMin": "Min. waarde", "schemas.fieldTypes.geolocation.description": "Coördinaten: lengte- en breedtegraad.", @@ -777,6 +779,7 @@ "schemas.fieldTypes.references.countMin": "Min. items", "schemas.fieldTypes.references.description": "Links naar andere inhoudsitems.", "schemas.fieldTypes.references.mustBePublished": "References must be published", + "schemas.fieldTypes.references.resolve": "Resolve references", "schemas.fieldTypes.references.resolveHint": "Toon de naam van het item waarnaar wordt verwezen in de inhoudslijst wanneer MaxItems is ingesteld op 1.", "schemas.fieldTypes.string.characters": "Karakters", "schemas.fieldTypes.string.charactersMax": "Max. karakters", @@ -941,6 +944,7 @@ "validation.min": "{field|upper} moet groter zijn dan of gelijk zijn aan '{min}'.", "validation.minlength": "{field|upper} moet minimaal {requiredlength} item (s) bevatten.", "validation.minlengthstring": "{field|upper} moet minimaal {requiredlength} teken (s) bevatten.", + "validation.notAnValidSvg": "The SVG is malicious and contains script tags.", "validation.pattern": "{field|upper} komt niet overeen met het patroon.", "validation.patternmessage": "{message}", "validation.required": "{field|upper} is vereist.", diff --git a/backend/i18n/frontend_zh.json b/backend/i18n/frontend_zh.json new file mode 100644 index 000000000..260f8f667 --- /dev/null +++ b/backend/i18n/frontend_zh.json @@ -0,0 +1,979 @@ +{ + "api.contentApi": "内容 API", + "api.generalApi": "通用 API", + "api.graphql": "GraphQL", + "api.graphqlPageTitle": "GraphQL", + "api.pageTitle": "API", + "api.title": "API", + "apps.allApps": "所有应用程序", + "apps.appLoadFailed": "加载应用失败。请重新加载。", + "apps.appNameHint": "您只能使用字母、数字和破折号,并且不能超过 40 个字符。", + "apps.appNameValidationMessage": "名称可以包含小写字母 (a-z)、数字和破折号。", + "apps.appNameWarning": "以后不能更改应用名称。", + "apps.appsButtonCreate": "应用概览", + "apps.appsButtonFallbackTitle": "应用概览", + "apps.archive": "存档应用", + "apps.archiveConfirmText": "你真的要存档这个应用程序吗?", + "apps.archiveConfirmTitle": "存档应用程序", + "apps.archiveFailed": "存档应用失败。请重新加载。", + "apps.archiveWarning": "一旦你归档了一个应用程序,就没有回头路了。请确定。", + "apps.create": "创建应用程序", + "apps.createBlankApp": "新应用程序", + "apps.createBlankAppDescription": "创建一个没有内容和Schemas的新空白应用程序。", + "apps.createBlogApp": "新博客示例", + "apps.createBlogAppDescription": "从我们准备使用的博客开始。", + "apps.createFailed": "创建应用失败。请重新加载。", + "apps.createProfileApp": "新配置文件示例", + "apps.createProfileAppDescription": "创建您的个人资料页面。", + "apps.createWithTemplate": "创建 {template} 示例", + "apps.empty": "您还没有与任何应用协作", + "apps.generalSettings": "通用", + "apps.generalSettingsDangerZone": "通用", + "apps.image": "图片", + "apps.imageDrop": "拖放上传", + "apps.leave": "离开应用程序", + "apps.leaveConfirmText": "你真的要离开这个应用程序吗?", + "apps.leaveConfirmTitle": "离开应用程序。", + "apps.leaveFailed": "退出应用失败,请重新加载。", + "apps.listPageTitle": "应用程序", + "apps.loadFailed": "加载应用失败。请重新加载。", + "apps.loadSettingsFailed": "更新界面设置失败。请重新加载。", + "apps.removeImage": "删除图片", + "apps.removeImageFailed": "删除应用图片失败。请重新加载。", + "apps.updateFailed": "更新应用失败。请重新加载。", + "apps.updateSettingsFailed": "更新界面设置失败。请重新加载。", + "apps.upgradeHintCurrent": "你在 {plan} 计划中。", + "apps.upgradeHintUpgrade": "升级!", + "apps.uploadImage": "拖放文件以替换应用图片。使用正方形大小。", + "apps.uploadImageButton": "上传文件", + "apps.uploadImageFailed": "上传图片失败,请重新加载。", + "apps.uploadImageTooBig": "应用图片太大。", + "apps.welcomeSubtitle": "欢迎来到 Squidex。", + "apps.welcomeTitle": "Hi {user}", + "appSettings.editors.deleteConfirmText": "你真的要删除这个编辑器 URL 吗?", + "appSettings.editors.deleteConfirmTitle": "删除编辑器 URL", + "appSettings.editors.empty": "尚未创建编辑器 URL。", + "appSettings.editors.title": "自定义编辑器", + "appSettings.hideScheduler": "隐藏预定发布对话框", + "appSettings.patterns.deleteConfirmText": "你真的要删除这个模式吗?", + "appSettings.patterns.deleteConfirmTitle": "删除模式", + "appSettings.patterns.empty": "尚未创建模式。", + "appSettings.patterns.title": "模式", + "appSettings.refreshTooltip": "刷新 UI 设置 (CTRL + SHIFT + R)", + "appSettings.reloaded": "UI 设置重新加载。", + "appSettings.title": "界面设置", + "assets.createFolder": "创建文件夹", + "assets.createFolderFailed": "资源文件夹创建失败,请重新加载。", + "assets.createFolderTooltip": "创建新文件夹 (CTRL + SHIFT + G)", + "assets.deleteConfirmText": "你真的要删除资源吗?", + "assets.deleteConfirmTitle": "删除资源", + "assets.deleteFailed": "资源删除失败,请重新加载。", + "assets.deleteFolderConfirmText": "您真的要删除文件夹和所有资源吗?", + "assets.deleteFolderConfirmTitle": "删除文件夹", + "assets.deleteMetadataConfirmText": "你真的要删除这个元数据吗?", + "assets.deleteMetadataConfirmTitle": "删除元数据", + "assets.deleteReferrerConfirmText": "资源被内容项引用。\n\n您真的要删除资源吗?", + "assets.deleteReferrerConfirmTitle": "删除资源", + "assets.downloadVersion": "下载此版本", + "assets.dropToUpdate": "下拉更新", + "assets.duplicateFile": "资源已经上传。", + "assets.editor.flipHorizontally": "水平翻转", + "assets.editor.flipVertically": "垂直翻转", + "assets.editor.focusPointLabel": "选择焦点位置", + "assets.editor.focusPointPreview": "不同尺寸的预览", + "assets.editor.rotateLeft": "向左旋转", + "assets.editor.rotateRight": "向右旋转", + "assets.fileTooBig": "资源太大。", + "assets.folderName": "文件夹名称", + "assets.folderNameHint": "文件夹名称用作显示名称,不能唯一。", + "assets.insertAssets": "插入资源", + "assets.linkSelected": "链接选定的资源 ({count})", + "assets.listPageTitle": "资源", + "assets.loadFailed": "资源加载失败,请重新加载。", + "assets.loadFoldersFailed": "加载资源文件夹失败。请重新加载。", + "assets.metadata": "元数据", + "assets.metadataAdd": "添加元数据", + "assets.moveFailed": "资源移动失败。请重新加载。", + "assets.protected": "受保护", + "assets.refreshTooltip": "刷新资源 (CTRL + SHIFT + R)", + "assets.reloaded": "资源重新加载。", + "assets.removeConfirmText": "你真的要移除资源吗?", + "assets.removeConfirmTitle": "移除资源", + "assets.renameFolder": "重命名文件夹", + "assets.replaceConfirmText": "你真的想用更新的版本替换资源吗", + "assets.replaceConfirmTitle": "替换资源?", + "assets.replaceFailed": "替换资源失败。请重新加载。", + "assets.searchByName": "按名称搜索", + "assets.searchByTags": "按标签搜索", + "assets.selectMany": "选择资源", + "assets.specialFolder.parent": "", + "assets.specialFolder.root": "", + "assets.tabFocusPoint": "焦点", + "assets.tabHistory": "历史", + "assets.tabImage": "图片", + "assets.tabMetadata": "元数据", + "assets.tabPreview": "预览", + "assets.tabTextEditor": "文本编辑器", + "assets.updated": "资源已更新。", + "assets.updateFailed": "更新资源失败。请重新加载。", + "assets.updateFolderFailed": "更新资源文件夹失败。请重新加载。", + "assets.uploadByDialog": "选择文件", + "assets.uploadByDrop": "将文件拖放到此处进行上传", + "assets.uploaderUploadHere": "没有正在进行的上传,将文件拖到这里。", + "assets.uploadFailed": "资源上传失败,请重新加载。", + "assets.uploadHint": "在现有项目上放置文件以使用更新版本替换资源。", + "backups.backupCountAssetsLabel": "资源", + "backups.backupCountAssetsTooltip": "存档资源", + "backups.backupCountEventsLabel": "事件", + "backups.backupCountEventsTooltip": "存档事件", + "backups.backupDownload": "下载", + "backups.backupDownloadLink": "准备就绪", + "backups.backupDuration": "持续时间", + "backups.deleteConfirmText": "你真的要删除备份吗?", + "backups.deleteConfirmTitle": "删除备份", + "backups.deleted": "备份即将被删除。", + "backups.deleteFailed": "删除备份失败。", + "backups.empty": "尚未创建备份。", + "backups.loadFailed": "加载备份失败。", + "backups.maximumReached": "您已达到最大备份数:10。", + "backups.refreshTooltip": "刷新备份 (CTRL + SHIFT + R)", + "backups.reloaded": "备份已重新加载。", + "backups.restore": "恢复备份", + "backups.restoreFailed": "无法开始恢复。", + "backups.restoreLastStatus": "上次还原操作", + "backups.restoreLastUrl": "要备份的网址", + "backups.restoreNewAppName": "可选的应用程序名称", + "backups.restorePageTitle": "恢复备份", + "backups.restoreStarted": "恢复开始,可能需要几分钟才能完成。", + "backups.restoreStartedLabel": "开始", + "backups.restoreStoppedLabel": "已停止", + "backups.restoreTitle": "恢复备份", + "backups.start": "开始备份", + "backups.started": "备份已开始,可能需要几分钟才能完成。", + "backups.startedLabel": "开始", + "backups.startFailed": "启动备份失败。", + "clients.add": "添加客户端", + "clients.addFailed": "添加客户端失败,请重新加载。", + "clients.allowAnonymous": "允许匿名访问。", + "clients.allowAnonymousHint": "允许在没有访问令牌的情况下访问通过此客户端角色配置的所有资源的 API。不要给多个客户端匿名访问。", + "clients.apiCallsLimit": "最大 API 调用数", + "clients.apiCallsLimitHint": "限制此客户端每月可以进行的 API 调用次数,以保护您的 API 队伍为其他更重要的客户端提供服务。", + "clients.clientIdValidationMessage": "名称只能包含字母、数字、破折号和空格。", + "clients.clientNamePlaceholder": "输入客户端名称", + "clients.connect": "连接", + "clients.connectWizard.cli": "连接 Squidex CLI", + "clients.connectWizard.cliHint": "下载 CLI 并连接到此应用程序以启动备份、同步Schemas或导出内容。", + "clients.connectWizard.cliStep1": "获取最新的 Squidex CLI", + "clients.connectWizard.cliStep1Download": "[从 Github 下载 CLI](https://github.com/Squidex/squidex-samples/releases)", + "clients.connectWizard.cliStep1Hint": "这些版本包含适用于所有主要操作系统的二进制文件,如果您安装了 .NET Core,则可以下载一小部分。", + "clients.connectWizard.cliStep2": "将 `<你的 Squidex CLI 下载目录>` 添加到你的 `$PATH` 变量中", + "clients.connectWizard.cliStep3": "在 CLI 配置中添加你的应用名称", + "clients.connectWizard.cliStep3Hint": "您可以在 CLI 中管理多个应用程序的配置并切换到一个应用程序。", + "clients.connectWizard.cliStep4": "在 CLI 中切换到您的应用程序", + "clients.connectWizard.manually": "手动连接", + "clients.connectWizard.manuallyHint": "获取如何与 Postman 或 curl 建立连接的说明。", + "clients.connectWizard.manuallyStep1": "使用 curl 获取令牌", + "clients.connectWizard.manuallyStep2": "只需使用以下令牌", + "clients.connectWizard.manuallyStep3": "将令牌作为 HTTP 标头添加到所有请求中", + "clients.connectWizard.manuallyTokenHint": "令牌通常会在 30 天后过期,但您可以请求多个令牌。", + "clients.connectWizard.postManDocs": "从 [文档](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman) 中的 Postman 教程开始。", + "clients.connectWizard.sdk": "使用 SDK 连接到您的应用程序", + "clients.connectWizard.sdkHelp": "你需要另一个 SDK?", + "clients.connectWizard.sdkHelpLink": "在支持论坛联系我们", + "clients.connectWizard.sdkHint": "下载 SDK 并建立与此应用程序的连接。", + "clients.connectWizard.sdkStep1": "安装.NET SDK", + "clients.connectWizard.sdkStep1Download": "SDK 可在 [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)", + "clients.connectWizard.sdkStep2": "创建客户端管理器", + "clients.connectWizard.step0Title": "设置客户端", + "clients.connectWizard.step1Title": "选择连接方式", + "clients.connectWizard.step2Title": "连接", + "clients.deleteConfirmText": "你真的要撤销客户端吗?", + "clients.deleteConfirmTitle": "撤销客户端", + "clients.empty": "尚未创建客户端。", + "clients.loadFailed": "加载客户端失败。请重新加载。", + "clients.refreshTooltip": "刷新客户端 (CTRL + SHIFT + R)", + "clients.reloaded": "客户端重新加载。", + "clients.revokeFailed": "撤销客户端失败。请重新加载。", + "clients.tokenFailed": "创建令牌失败。请重试。", + "comments.create": "创建评论", + "comments.createFailed": "创建评论失败。", + "comments.deleteConfirmText": "你真的要删除评论吗?", + "comments.deleteConfirmTitle": "删除评论", + "comments.deleteFailed": "删除评论失败。", + "comments.follow": "关注", + "comments.loadFailed": "加载评论失败。", + "comments.title": "评论", + "comments.updateFailed": "更新评论失败。", + "common.actions": "动作", + "common.administration": "管理", + "common.administrationPageTitle": "管理", + "common.api": "API", + "common.apps": "应用程序", + "common.aspectRatio": "纵横比", + "common.assets": "资源", + "common.back": "返回", + "common.backendError": "后端错误", + "common.backups": "备份", + "common.bookmarks": "书签", + "common.bytes": "bytes", + "common.cancel": "取消", + "common.category": "类别", + "common.clear": "清除", + "common.clientId": "客户端 ID", + "common.clients": "客户端", + "common.clientSecret": "客户端密码", + "common.clipboardAdded": "值已添加到您的剪贴板。", + "common.clone": "克隆", + "common.cluster": "集群", + "common.clusterPageTitle": "集群", + "common.comments": "评论", + "common.components": "组件", + "common.confirm": "确认", + "common.consumers": "消费者", + "common.content": "内容", + "common.contents": "内容", + "common.continue": "继续", + "common.contributors": "贡献者", + "common.create": "创建", + "common.created": "创建", + "common.date": "日期", + "common.dateTimeEditor.local": "本地", + "common.dateTimeEditor.now": "现在", + "common.dateTimeEditor.nowTooltip": "现在使用 (UTC)", + "common.dateTimeEditor.today": "今天", + "common.dateTimeEditor.todayTooltip": "使用今天 (UTC)", + "common.dateTimeEditor.utc": "UTC", + "common.delete": "删除", + "common.description": "说明", + "common.designer": "设计师", + "common.disabled": "已禁用", + "common.displayName": "显示名称", + "common.edit": "编辑", + "common.email": "电子邮件", + "common.enabled": "已启用", + "common.error": "错误", + "common.errorBack": "返回上一页。", + "common.errorNoPermission": "您无权执行此操作。", + "common.errorNotFound": "未找到", + "common.event": "事件", + "common.events": "事件", + "common.executed": "已执行", + "common.expertMode": "专家模式", + "common.extension": "扩展名", + "common.failed": "失败", + "common.fallback": "后备", + "common.field": "字段", + "common.files": "文件", + "common.filters": "过滤器", + "common.folder": "文件夹", + "common.folders": "文件夹", + "common.generalSettings": "通用", + "common.generate": "生成", + "common.github": "Github", + "common.height": "高度", + "common.help": "帮助", + "common.helpTour": "单击帮助图标以显示上下文特定的帮助页面。转到", + "common.hide": "隐藏", + "common.hints": "提示", + "common.history": "历史", + "common.httpConflict": "更新失败。其他用户进行了更改。请重新加载。", + "common.httpLimit": "您已超出 API 调用的最大限制。", + "common.id": "身份", + "common.label": "标签", + "common.language": "语言", + "common.languages": "语言", + "common.latitudeShort": "纬度", + "common.loading": "正在加载", + "common.logout": "注销", + "common.logs": "日志", + "common.longitudeShort": "Lon", + "common.mapHide": "隐藏地图", + "common.mapShow": "显示地图", + "common.message": "消息", + "common.name": "名称", + "common.no": "不", + "common.nothingChanged": "什么都没有改变。", + "common.noValue": "- 无值 -", + "common.or": "或", + "common.pagerInfo": "{itemFirst}-{itemLast} 的 {numberOfItems}", + "common.password": "密码", + "common.passwordConfirm": "确认密码", + "common.pattern": "模式", + "common.patterns": "模式", + "common.permissions": "权限", + "common.preview": "预览", + "common.product": "Squidex Headless CMS", + "common.project": "项目", + "common.queryOperators.contains": "包含", + "common.queryOperators.empty": "为空", + "common.queryOperators.endsWith": "以", + "common.queryOperators.eq": "等于", + "common.queryOperators.exists": "exists", + "common.queryOperators.ge": "大于或等于", + "common.queryOperators.gt": "大于", + "common.queryOperators.le": "小于或等于", + "common.queryOperators.lt": "小于", + "common.queryOperators.matchs": "匹配", + "common.queryOperators.ne": "不等于", + "common.queryOperators.startsWith": "开始于", + "common.refresh": "刷新", + "common.remember": "不要再问了", + "common.rename": "重命名", + "common.requiredHint": "必需的", + "common.reset": "重置", + "common.restore": "恢复", + "common.role": "角色", + "common.roles": "角色", + "common.rule": "规则", + "common.rules": "规则", + "common.sampleCodeLabel": "示例代码在", + "common.save": "保存", + "common.saveShortcut": "CTRL + S", + "common.schemas": "Schemas", + "common.search": "搜索", + "common.searchGoogleMaps": "搜索谷歌地图", + "common.searchResults": "搜索结果", + "common.separateByLine": "按行分隔", + "common.settings": "设置", + "common.sidebar": "侧边栏扩展", + "common.sidebarTour": "侧边栏导航包含有用的上下文特定链接。您可以在此处查看此Schemas随时间变化的历史记录。", + "common.skipped": "Skipped", + "common.slug": "弹头", + "common.stars.max": "不得超过 15 颗星", + "common.status": "状态", + "common.statusChangeTo": "更改为", + "common.submit": "提交", + "common.subscription": "订阅", + "common.succeeded": "成功", + "common.tagAdd": ", 添加标签", + "common.tagAddReference": ", 添加引用", + "common.tagAddSchema": ", 添加Schemas", + "common.tags": "标签", + "common.tagsAll": "所有标签", + "common.time": "时间", + "common.update": "更新", + "common.upload": "上传", + "common.url": "URL", + "common.users": "用户", + "common.value": "值", + "common.width": "宽度", + "common.workflow": "工作流程", + "common.workflows": "工作流程", + "common.yes": "是", + "contents.addComponent": "添加组件", + "contents.arrayAddItem": "添加项目", + "contents.arrayClear": "清除", + "contents.arrayClearConfirmText": "你真的要清空数组吗?", + "contents.arrayClearConfirmTitle": "清除数组", + "contents.arrayCloneItem": "克隆这个项目", + "contents.arrayCollapseAll": "折叠所有项目", + "contents.arrayCollapseItem": "收起此项", + "contents.arrayExpandAll": "展开所有项目", + "contents.arrayExpandItem": "展开此项", + "contents.arrayMoveBottom": "将此项目移到底部", + "contents.arrayMoveDown": "将此项向下移动", + "contents.arrayMoveTop": "将此项移至顶部", + "contents.arrayMoveUp": "将此项向上移动", + "contents.arrayNoFields": "先添加一个嵌套字段来添加项目。", + "contents.assetsUpload": "删除文件或点击", + "contents.autotranslate": "从母语自动翻译", + "contents.bulkFailed": "删除或更新内容失败。请重新加载。", + "contents.changeStatusTo": "将内容项更改为 {action}", + "contents.changeStatusToImmediately": "立即设置为 {action}。", + "contents.changeStatusToLater": "在稍后的日期和时间设置为 {action}。", + "contents.componentNoSchema": "添加至少一个Schemas来设置组件。", + "contents.componentsNoSchema": "添加至少一个Schemas来添加组件。", + "contents.contentNotValid": "内容元素无效,请用所有语言(如果可本地化)检查左侧带有红色条的字段。", + "contents.contentTab.editor": "编辑器", + "contents.contentTab.references": "参考资料", + "contents.contentTab.referencing": "引用", + "contents.create": "新建", + "contents.createContentTooltip": "新建内容 (CTRL + SHIFT + G)", + "contents.created": "内容创建成功。", + "contents.createdByFieldDescription": "创建内容项的用户。", + "contents.createFailed": "创建内容失败,请重新加载。", + "contents.createFieldDescription": "创建内容项的日期时间。", + "contents.createPageTitle": "创建内容", + "contents.createTitle": "新内容", + "contents.currentStatusLabel": "当前版本", + "contents.deleteConfirmText": "你真的要删除内容吗?", + "contents.deleteConfirmTitle": "删除内容", + "contents.deleteManyConfirmText": "您真的要删除选定的内容项吗?", + "contents.deleteReferrerConfirmText": "该内容被另一个内容项引用。\n\n您真的要删除该内容吗?", + "contents.deleteReferrerConfirmTitle": "删除内容", + "contents.deleteVersionConfirmText": "你真的要删除这个版本吗?", + "contents.deleteVersionFailed": "删除版本失败。请重新加载。", + "contents.draftNew": "新草稿", + "contents.draftStatus": "新版本", + "contents.editPageTitle": "编辑内容", + "contents.invariantFieldDescription": "内容项的 '{fieldName}' 字段。", + "contents.languageModeAll": "所有语言", + "contents.languageModeSingle": "单一语言", + "contents.lastModifiedByFieldDescription": "上次修改内容项的用户。", + "contents.lastModifiedFieldDescription": "上次修改内容项的日期时间。", + "contents.lastUpdatedLabel": "上次更新", + "contents.loadContent": "加载", + "contents.loadContentFailed": "加载内容失败,请重新加载。", + "contents.loadDataFailed": "加载数据失败,请重新加载。", + "contents.loadFailed": "加载内容失败,请重新加载。", + "contents.loadVersionFailed": "加载新版本失败。请重新加载。", + "contents.localizedFieldDescription": "内容项的 '{fieldName}' 字段(本地化)。", + "contents.newStatusFieldDescription": "内容项的新状态。", + "contents.noReference": "- 无引用 -", + "contents.noReferences": "此内容没有引用。", + "contents.noReferencing": "此内容未被其他项目引用。", + "contents.pendingChangesTextToChange": "您有未保存的更改。\n\n当您更改状态时,您将丢失它们。\n\n**仍要继续吗?**", + "contents.pendingChangesTextToClose": "您有未保存的更改。\n\n当您关闭当前内容视图时,您将丢失它们。\n\n**您是否仍要继续?**", + "contents.pendingChangesTextToPreview": "您有未保存的更改。\n\n您不会在预览中看到它们。\n\n**您是否仍要继续?**", + "contents.pendingChangesTitle": "未保存的更改", + "contents.publishAll": "全部发布", + "contents.referencesCreateNew": "新增", + "contents.referencesCreatePublish": "创建和发布", + "contents.referencesLink": "链接所选内容 ({count})", + "contents.referencesSelectExisting": "选择现有", + "contents.referencesSelectSchema": "选择 {schema}", + "contents.refreshTooltip": "刷新内容 (CTRL + SHIFT + R)", + "contents.reloaded": "内容已重新加载。", + "contents.removeConfirmText": "您真的要删除内容吗?", + "contents.removeConfirmTitle": "删除内容", + "contents.saveAndPublish": "保存并发布", + "contents.scheduledAt": "at", + "contents.scheduledAtLabel": "at", + "contents.scheduledTo": "to", + "contents.schemasPageTitle": "内容", + "contents.searchPlaceholder": "全文搜索", + "contents.searchSchemasPlaceholder": "搜索Schemas...", + "contents.selectionCount": "{count} 个选定的项目", + "contents.statusFieldDescription": "内容项的状态。", + "contents.statusQueries": "状态查询", + "contents.stockPhotoEmpty": "未选择任何内容", + "contents.stockPhotoSearch": "通过 Unsplash 搜索照片", + "contents.tableHeaders.created": "创建", + "contents.tableHeaders.createdBy": "创建者", + "contents.tableHeaders.createdByShort": "By", + "contents.tableHeaders.id": "Id", + "contents.tableHeaders.lastModified": "更新", + "contents.tableHeaders.lastModifiedBy": "更新者", + "contents.tableHeaders.lastModifiedByShort": "By", + "contents.tableHeaders.nextStatus": "下一个状态", + "contents.tableHeaders.status": "状态", + "contents.tableHeaders.version": "版本", + "contents.unpublishReferrerConfirmText": "该内容被另一个已发布的内容项引用。\n\n您真的要取消发布此内容吗?", + "contents.unpublishReferrerConfirmTitle": "取消发布内容", + "contents.unsavedChangesText": "您有未保存的更改。要立即加载吗?", + "contents.unsavedChangesTitle": "未保存的更改", + "contents.unsetValue": "未设置值", + "contents.unsetValueConfirmText": "如果您取消设置该值,您可能会丢失更改。\n\n您真的要这样做吗?", + "contents.unsetValueConfirmTitle": "你想取消设置吗?", + "contents.updated": "内容更新成功。", + "contents.updateFailed": "更新内容失败,请重新加载。", + "contents.validate": "验证", + "contents.validationHint": "当您看到验证错误时,请记住检查所有语言。", + "contents.versionCompare": "比较", + "contents.versionDelete": "删除此版本", + "contents.versionFieldDescription": "内容项的版本", + "contents.versionViewing": "查看版本**{version}**。", + "contents.viewLatest": "查看最新", + "contents.viewReset": "重置默认视图", + "contributors.add": "添加贡献者", + "contributors.addFailed": "添加贡献者失败。请重新加载。", + "contributors.contributorAssigned": "一个具有输入电子邮件地址的新用户已被创建并被指定为贡献者。", + "contributors.contributorAssignedExisting": "用户已被分配", + "contributors.contributorAssignedInvited": "用户已被邀请并分配。", + "contributors.contributorAssignedOld": "用户已被添加为贡献者。", + "contributors.deleteConfirmText": "你真的要移除贡献者吗?", + "contributors.deleteConfirmTitle": "移除贡献者", + "contributors.deleteFailed": "删除贡献者失败。请重新加载。", + "contributors.emailPlaceholder": "查找现有用户或通过电子邮件邀请", + "contributors.empty": "没有找到贡献者。", + "contributors.import.emailsDetected": "检测到的电子邮件:{count}", + "contributors.import.run": "添加贡献者", + "contributors.import.run2": "导入", + "contributors.importButton": "一次添加多个贡献者", + "contributors.importHintg": "大团队?", + "contributors.importTitle": "导入贡献者", + "contributors.loadFailed": "加载贡献者失败。请重新加载。", + "contributors.planHint": "您的计划允许最多 {maxContributors} 个贡献者。", + "contributors.refreshTooltip": "刷新贡献者 (CTRL + SHIFT + R)", + "contributors.reloaded": "贡献者重新加载。", + "contributors.search": "搜索", + "contributors.userNotFound": "用户不存在。", + "dashboard.apiCallsCard": "API 调用", + "dashboard.apiCallsChart": "API 调用图表", + "dashboard.apiCallsLimitLabel": "每月限制", + "dashboard.apiCallsSummaryCard": "API 调用摘要", + "dashboard.apiDocumentationCard": "API 文档", + "dashboard.apiPerformanceCard": "API 性能 (ms): {summary}ms avg", + "dashboard.apiPerformanceChart": "API 性能图表", + "dashboard.assetSizeCard": "资源大小 (MB", + "dashboard.assetSizeLabel": "总大小", + "dashboard.assetSizeLimitLabel": "总限制", + "dashboard.assetTotalSize": "资源总存储大小", + "dashboard.assetUpdloadsCountChart": "资源上传计数图表", + "dashboard.assetUploadsCard": "资源上传", + "dashboard.assetUploadsSizeChart": "资源上传大小图表", + "dashboard.configSaved": "配置已保存。", + "dashboard.contentApi": "内容 API", + "dashboard.contentApiDescription": "适用于您的应用内容的 OpenAPI 3.0 兼容文档。", + "dashboard.contentsSummaryCard": "项目数量", + "dashboard.currentMonthLabel": "本月", + "dashboard.downloadLog": "下载日志", + "dashboard.editConfig": "编辑配置", + "dashboard.githubCard": "Github", + "dashboard.githubCardDescription": "从 Github 获取源代码并报告错误或寻求支持。", + "dashboard.historyCard": "历史", + "dashboard.pageTitle": "仪表盘", + "dashboard.resetConfigConfirmText": "您真的要将仪表板重置为默认设置吗?", + "dashboard.resetConfigConfirmTitle": "重置配置", + "dashboard.schemaNewCard": "新Schemas", + "dashboard.schemaNewCardDescription": "Schemas定义了内容元素的结构。", + "dashboard.schemasCard": "Schemas", + "dashboard.schemasCardDescription": "深入了解此应用的数据模型。", + "dashboard.stackedChart": "折叠", + "dashboard.supportCard": "反馈与支持", + "dashboard.supportCardDescription": "提供反馈和请求功能以帮助我们改进 Squidex。", + "dashboard.trafficChart": "API 流量图表", + "dashboard.trafficHeader": "流量 (MB)", + "dashboard.trafficLimitLabel": "每月限制", + "dashboard.trafficSummaryCard": "API 流量汇总", + "dashboard.welcomeText": "欢迎使用 **{app}** 仪表板。", + "dashboard.welcomeTitle": "Hi {user}", + "eventConsumers.count": "计数", + "eventConsumers.loadFailed": "加载事件消费者失败。请重新加载。", + "eventConsumers.pageTitle": "事件消费者", + "eventConsumers.position": "位置", + "eventConsumers.refreshTooltip": "刷新事件消费者 (CTRL + SHIFT + R)", + "eventConsumers.reloaded": "事件消费者重新加载。", + "eventConsumers.resetFailed": "无法重置事件消费者。请重新加载。", + "eventConsumers.resetTooltip": "重置事件消费者", + "eventConsumers.startFailed": "无法启动事件消费者。请重新加载。", + "eventConsumers.startTooltip": "启动事件消费者", + "eventConsumers.stopFailed": "无法停止事件消费者。请重新加载。", + "eventConsumers.stopTooltip": "停止事件消费者", + "features.loadFailed": "加载功能失败。请重新加载。", + "history.loadFailed": "加载历史记录失败。请重新加载。", + "history.title": "活动", + "languages.add": "添加语言", + "languages.addFailed": "添加语言失败。请重新加载。", + "languages.deleteConfirmText": "你真的要删除语言吗?", + "languages.deleteConfirmTitle": "删除语言", + "languages.deleteFailed": "删除语言失败。请重新加载。", + "languages.loadFailed": "加载语言失败。请重新加载。", + "languages.master": "是大师", + "languages.masterHint": "如果没有定义回退,其他语言回退到母版。", + "languages.optional": "是可选的", + "languages.optionalHint": "不得输入可选语言的值,即使字段是必需的。", + "languages.refreshTooltip": "刷新语言 (CTRL + SHIFT + R)", + "languages.reloaded": "语言已重新加载。", + "languages.updateFailed": "更改语言失败。请重新加载。", + "news.headline": "有什么新鲜事?", + "news.title": "新功能", + "notifo.subscripeTooltip": "单击此按钮可订阅所有更改并接收推送通知。", + "plans.billingPortal": "计费门户", + "plans.billingPortalHint": "前往账单门户查看付款历史和订阅概览。", + "plans.change": "改变", + "plans.changeConfirmTitle": "更改订阅", + "plans.changeFailed": "更改计划失败。请重新加载。", + "plans.includedCalls": "API 调用", + "plans.includedContributors": "贡献者", + "plans.includedStorage": "存储", + "plans.includedTraffic": "交通", + "plans.loadFailed": "加载计划失败。请重新加载。", + "plans.noPlanConfigured": "未配置计划,此应用无限制使用。", + "plans.notPlanOwner": "您尚未创建订阅。因此您无法更改计划。", + "plans.perMonth": "每月", + "plans.perYear": "每年", + "plans.refreshTooltip": "刷新计划 (CTRL + SHIFT + R)", + "plans.reloaded": "计划重新加载。", + "plans.selected": "已选择", + "profile.title": "个人资料", + "profile.userEmail": "登录方式", + "roles.add": "添加角色", + "roles.addFailed": "添加角色失败,请重新加载。", + "roles.default.owner": "可以做任何事情,包括删除应用程序。", + "roles.default.reader": "只能读取资源和内容。", + "roles.defaults.developer": "可以使用 API 视图,编辑资源、内容、Schemas、规则、工作流和 appSettings.patterns。", + "roles.defaults.editor": "可以编辑资源和内容并查看工作流程。", + "roles.deleteConfirmText": "删除角色", + "roles.deleteConfirmTitle": "你真的要删除角色吗?", + "roles.loadFailed": "加载角色失败。请重新加载。", + "roles.loadPermissionsFailed": "加载权限失败。请重新加载。", + "roles.permissions": "权限", + "roles.permissionsDescription": "权限在 API 级别限制允许的操作和查询,是一项安全功能。", + "roles.permissionsPlaceholder": "开始输入以搜索权限", + "roles.properties": "属性", + "roles.properties.hideAPI": "隐藏 API", + "roles.properties.hideAssets": "隐藏资源", + "roles.properties.hideContents": "隐藏 {schema} 内容", + "roles.properties.hideSchemas": "隐藏Schemas", + "roles.properties.hideSettings": "隐藏设置", + "roles.propertiesDescription": "属性描述管理 UI 的行为,但不为 API 提供安全性。", + "roles.refreshTooltip": "刷新角色 (CTRL + SHIFT + R)", + "roles.reloaded": "重新加载角色。", + "roles.revokeFailed": "撤销角色失败,请重新加载。", + "roles.roleNamePlaceholder": "输入角色名称", + "roles.updateFailed": "更新角色失败。请重新加载。", + "rules.actionData": "动作数据", + "rules.actionHint": "动作类型的选择以后不能更改。", + "rules.cancelFailed": "取消规则失败,请重新加载。", + "rules.create": "新规则", + "rules.createFailed": "创建规则失败。请重新加载。", + "rules.createTooltip": "新规则 (CTRL + SHIFT + G)", + "rules.deleteConfirmText": "你真的要删除规则吗?", + "rules.deleteConfirmTitle": "删除规则", + "rules.deleteFailed": "删除规则失败,请重新加载。", + "rules.empty": "尚未创建规则。", + "rules.emptyAddRule": "添加规则", + "rules.enqueued": "规则已加入队列。", + "rules.itemPageTitle": "规则", + "rules.listPageTitle": "规则", + "rules.loadFailed": "加载规则失败。请重新加载。", + "rules.readMore": "阅读更多", + "rules.refreshEventsTooltip": "刷新事件 (CTRL + SHIFT + R)", + "rules.refreshTooltip": "刷新规则 (CTRL + SHIFT + R)", + "rules.reloaded": "规则重新加载。", + "rules.restarted": "规则将在几秒钟后开始运行。", + "rules.ruleEvents.cancelFailed": "取消规则事件失败。请重新加载。", + "rules.ruleEvents.enqueue": "入队", + "rules.ruleEvents.enqueued": "事件已入队。将在几秒钟后重新发送。", + "rules.ruleEvents.enqueueFailed": "无法将规则事件入队。请重新加载。", + "rules.ruleEvents.lastInvokedLabel": "上次调用", + "rules.ruleEvents.listPageTitle": "规则事件", + "rules.ruleEvents.loadFailed": "加载事件失败。请重新加载。", + "rules.ruleEvents.nextAttemptLabel": "下一个", + "rules.ruleEvents.numAttemptsLabel": "尝试次数", + "rules.ruleEvents.reloaded": "RuleEvents 重新加载。", + "rules.ruleSimulator.listPageTitle": "模拟器", + "rules.ruleSyntax.if": "如果", + "rules.ruleSyntax.then": "那么", + "rules.run": "运行", + "rules.runFailed": "运行规则失败。请重新加载。", + "rules.runFromSnapshots": "以最新状态运行", + "rules.runningRule": "规则 '{name}' 当前正在运行。", + "rules.runRuleConfirmText": "你真的想为所有事件运行规则吗?", + "rules.runRuleConfirmTitle": "运行规则", + "rules.simulate": "模拟", + "rules.simulateTooltip": "使用最近 100 个事件模拟此规则。", + "rules.simulator": "模拟器", + "rules.stop": "规则很快就会停止。", + "rules.triggerConfirmText": "你真的要触发规则吗?", + "rules.triggerConfirmTitle": "触发规则", + "rules.triggerFailed": "触发规则失败。请重新加载。", + "rules.triggerHint": "以后不能更改触发器类型的选择。", + "rules.unnamed": "未命名规则", + "rules.updateFailed": "更新规则失败。请重新加载。", + "schemas.addField": "添加字段", + "schemas.addFieldAndClose": "创建并关闭", + "schemas.addFieldAndCreate": "创建并添加字段", + "schemas.addFieldAndEdit": "创建和编辑字段", + "schemas.addFieldButton": "添加字段", + "schemas.addFieldFailed": "添加字段失败。请重新加载。", + "schemas.addNestedField": "添加嵌套字段", + "schemas.changeCategoryFailed": "更改类别失败。请重新加载。", + "schemas.clone": "Clone Schema", + "schemas.contentEditorUrl": "内容编辑器扩展", + "schemas.contentSidebarUrl": "内容侧边栏扩展", + "schemas.contentSidebarUrlHint": "详细信息视图中侧边栏插件的 URL。", + "schemas.contentsSidebarUrl": "内容侧边栏扩展", + "schemas.contentsSidebarUrlHint": "列表视图中侧边栏插件的 URL。", + "schemas.contextMenuTour": "打开上下文菜单以删除Schemas或为内容更改创建一些脚本。", + "schemas.create": "Create Schema", + "schemas.createCategory": "创建新类别...", + "schemas.createFailed": "无法创建Schemas。请重新加载。", + "schemas.createSchemaTooltip": "新Schemas (CTRL + SHIFT + G)", + "schemas.deleteConfirmText": "您真的要删除Schemas吗?", + "schemas.deleteConfirmTitle": "删除Schemas", + "schemas.deleteFailed": "删除Schemas失败。请重新加载。", + "schemas.deleteFieldFailed": "删除字段失败。请重新加载。", + "schemas.deleteRuleConfirmText": "你真的要删除这个字段规则吗?", + "schemas.deleteRuleConfirmTitle": "删除字段规则", + "schemas.deleteUrlConfirmText": "你真的要删除这个 URL 吗?", + "schemas.deleteUrlConfirmTitle": "删除 URL", + "schemas.disableFieldFailed": "禁用字段失败。请重新加载。", + "schemas.enableFieldFailed": "无法启用字段。请重新加载。", + "schemas.export.deleteFields": "删除字段", + "schemas.export.recreateFields": "重新创建字段", + "schemas.export.synchronize": "同步", + "schemas.field.allowedValues": "允许的值", + "schemas.field.defaultValue": "Default Value", + "schemas.field.defaultValues": "默认值", + "schemas.field.defaultValuesHint": "设置每种语言的默认值并覆盖默认值属性(如果已定义)。仅在真正需要时才使用它。", + "schemas.field.deleteConfirmText": "您真的要删除该字段吗?", + "schemas.field.deleteConfirmTitle": "删除字段", + "schemas.field.disable": "在 UI 中禁用", + "schemas.field.disabledMarker": "已禁用", + "schemas.field.editor": "Editor", + "schemas.field.editorUrl": "编辑器网址", + "schemas.field.editorUrlHint": "如果您使用自定义编辑器,请访问您的插件的 URL。", + "schemas.field.empty": "尚未创建字段。", + "schemas.field.enable": "在 UI 中启用", + "schemas.field.enabledMarker": "已启用", + "schemas.field.halfWidth": "半宽", + "schemas.field.halfWidthHint": "在编辑或创建页面上,当有足够的空间时,只显示半宽的字段。", + "schemas.field.hiddenMarker": "Hidden", + "schemas.field.hide": "隐藏在 API", + "schemas.field.hintsHint": "为文档和 UI 描述这个字段。", + "schemas.field.inlineEditable": "内联可编辑", + "schemas.field.labelHint": "文档和 UI 的显示名称。", + "schemas.field.localizable": "Localizable", + "schemas.field.localizableHint": "您可以将字段标记为可本地化。这意味着这取决于语言,例如城市名称。", + "schemas.field.localizableMarker": "localizable", + "schemas.field.lock": "锁定并防止更改", + "schemas.field.lockConfirmText": "警告:无法撤消锁定字段!锁定的字段定义无法再解锁、删除或更改。\n\n您真的要锁定此字段吗?", + "schemas.field.lockConfirmTitle": "锁定字段", + "schemas.field.lockedMarker": "锁定", + "schemas.field.nameHint": "API 响应中的字段名称。", + "schemas.field.namePlaceholder": "输入字段名称", + "schemas.field.nameValidationMessage": "名称必须是骆驼大小写的有效 javascript 名称。", + "schemas.field.placeholder": "Placeholder", + "schemas.field.placeholderHint": "定义输入控件的占位符。", + "schemas.field.required": "Required", + "schemas.field.requiredOnPublish": "发布时需要", + "schemas.field.show": "在 API 中显示", + "schemas.field.tabCommon": "通用", + "schemas.field.tabEditing": "Editing", + "schemas.field.tabValidation": "Validation", + "schemas.field.tagsHint": "为自动化流程注释您的领域的标签。", + "schemas.field.unique": "Unique", + "schemas.field.visibleMarker": "Visible", + "schemas.fieldTypes.array.count": "Items", + "schemas.fieldTypes.array.countMax": "最大项目数", + "schemas.fieldTypes.array.countMin": "最小项目", + "schemas.fieldTypes.array.description": "嵌入对象列表。", + "schemas.fieldTypes.assets.allowDuplicates": "允许重复值", + "schemas.fieldTypes.assets.count": "计数", + "schemas.fieldTypes.assets.countMax": "最大资源", + "schemas.fieldTypes.assets.countMin": "最小资源", + "schemas.fieldTypes.assets.description": "图片、视频、文档。", + "schemas.fieldTypes.assets.fileExtensions": "文件扩展名", + "schemas.fieldTypes.assets.folderId": "文件夹", + "schemas.fieldTypes.assets.folderIdHint": "新资源将上传到的资源文件夹。", + "schemas.fieldTypes.assets.mustBeImage": "必须是图片", + "schemas.fieldTypes.assets.previewFileName": "仅文件名", + "schemas.fieldTypes.assets.previewImage": "如果不是图像,则只有缩略图或文件名", + "schemas.fieldTypes.assets.previewImageAndFileName": "缩略图和文件名", + "schemas.fieldTypes.assets.previewMode": "PreviewMode", + "schemas.fieldTypes.assets.previewModeHint": "内容列表中资源的预览模式。", + "schemas.fieldTypes.assets.resolve": "解析第一个资源", + "schemas.fieldTypes.assets.resolveHint": "显示内容列表中第一个引用的资源。", + "schemas.fieldTypes.assets.size": "Size", + "schemas.fieldTypes.assets.sizeMax": "最小尺寸", + "schemas.fieldTypes.assets.sizeMin": "最大尺寸", + "schemas.fieldTypes.boolean.description": "是或否,对或错。", + "schemas.fieldTypes.component.description": "在此内容中嵌入另一个Schemas。", + "schemas.fieldTypes.components.description": "将其他Schemas作为数组嵌入到此内容中。", + "schemas.fieldTypes.dateTime.defaultMode": "默认模式", + "schemas.fieldTypes.dateTime.description": "活动日期,开放时间。", + "schemas.fieldTypes.dateTime.format": "Pattern", + "schemas.fieldTypes.dateTime.formatHint": "The pattern when shown in the UI, see: https://date-fns.org/v2.22.1/docs/format", + "schemas.fieldTypes.dateTime.rangeMax": "最大值", + "schemas.fieldTypes.dateTime.rangeMin": "最小值", + "schemas.fieldTypes.geolocation.description": "坐标:纬度和经度。", + "schemas.fieldTypes.json.description": "JSON 格式的数据,供开发人员使用。", + "schemas.fieldTypes.number.description": "ID、订单号、评级、数量。", + "schemas.fieldTypes.number.range": "范围", + "schemas.fieldTypes.number.rangeMax": "最大值", + "schemas.fieldTypes.number.rangeMin": "最小值", + "schemas.fieldTypes.references.count": "项目", + "schemas.fieldTypes.references.countMax": "最大项目数", + "schemas.fieldTypes.references.countMin": "最小项目", + "schemas.fieldTypes.references.description": "链接到其他内容项。", + "schemas.fieldTypes.references.mustBePublished": "必须发布参考文献", + "schemas.fieldTypes.references.resolve": "Resolve references", + "schemas.fieldTypes.references.resolveHint": "当 MaxItems 设置为 1 时,在内容列表中显示引用项的名称。", + "schemas.fieldTypes.string.characters": "字符", + "schemas.fieldTypes.string.charactersMax": "最大字符数", + "schemas.fieldTypes.string.charactersMin": "最小字符数", + "schemas.fieldTypes.string.contentType": "内容类型", + "schemas.fieldTypes.string.description": "标题、名称、段落。", + "schemas.fieldTypes.string.folderId": "资源文件夹", + "schemas.fieldTypes.string.folderIdHint": "新资源将上传到的资源文件夹。", + "schemas.fieldTypes.string.length": "Length", + "schemas.fieldTypes.string.lengthMax": "Max Length", + "schemas.fieldTypes.string.lengthMin": "最小长度", + "schemas.fieldTypes.string.pattern": "Regex Pattern", + "schemas.fieldTypes.string.patternMessage": "模式消息", + "schemas.fieldTypes.string.suggestions": "建议", + "schemas.fieldTypes.string.wordHint": "字数和字符数在纯文本上计算。纯文本根据定义的内容类型计算,可以是 Markdown 或 HTML。", + "schemas.fieldTypes.string.words": "单词", + "schemas.fieldTypes.string.wordsMax": "最大字数", + "schemas.fieldTypes.string.wordsMin": "最小字数", + "schemas.fieldTypes.tags.count": "项目", + "schemas.fieldTypes.tags.countMax": "最大项目数", + "schemas.fieldTypes.tags.countMin": "最小项目", + "schemas.fieldTypes.tags.description": "标签的特殊格式。", + "schemas.fieldTypes.ui.description": "编辑 UI 的分隔符。", + "schemas.hideFieldFailed": "隐藏字段失败。请重新加载。", + "schemas.import": "导入Schemas", + "schemas.listFields": "列表字段", + "schemas.listFieldsEmpty": "将字段拖放到此处或重新排序以显示内容列表中的字段。当未定义列表字段时,使用第一个字段。", + "schemas.loadFailed": "加载Schemas失败。请重新加载。", + "schemas.loadSchemaFailed": "Failed to load schema. Please reload.", + "schemas.lockFieldFailed": "无法锁定字段。请重新加载。", + "schemas.modeComponent": "Component", + "schemas.modeComponentDescription": "只能嵌入到组件字段...", + "schemas.modeMultiple": "多个内容", + "schemas.modeMultipleDescription": "最适合多个实例,如博客文章、页面、作者、产品...", + "schemas.modeSingle": "单一内容", + "schemas.modeSingleDescription": "最适合单个实例,如主页、隐私政策、设置...", + "schemas.nameWarning": "这些值以后不能更改。", + "schemas.previewUrls.empty": "未配置预览网址。", + "schemas.previewUrls.help": "查看集成帮助页面以了解有关预览 URL 的更多信息。", + "schemas.previewUrls.namePlaceholder": "网络或移动", + "schemas.previewUrls.title": "预览网址", + "schemas.previewUrls.urlPlaceholder": "带变量的 URL", + "schemas.published": "Published", + "schemas.publishedTour": "请注意,您必须先发布Schemas,然后才能向其中添加内容。", + "schemas.publishFailed": "无法发布Schemas。请重新加载。", + "schemas.referenceFields": "参考字段", + "schemas.referenceFieldsEmpty": "将字段拖放到此处或重新排序以在被其他内容引用时显示字段。当未定义引用字段时,将使用列表字段代替。", + "schemas.reloaded": "Schemas reloaded.", + "schemas.reorderFieldsFailed": "重新排序字段失败。请重新加载。", + "schemas.rules.action": "动作", + "schemas.rules.condition": "Javascript 中的条件", + "schemas.rules.empty": "没有配置字段规则。", + "schemas.rules.title": "字段规则", + "schemas.rules.when": "何时", + "schemas.saved": "Schema saved successfully.", + "schemas.saveFieldAndClose": "保存并关闭", + "schemas.saveFieldAndNew": "保存并添加字段", + "schemas.schemaHintsHint": "为文档和用户界面描述这个Schemas。", + "schemas.schemaLabelHint": "文档和用户界面的显示名称。", + "schemas.schemaNameHint": "您只能使用字母、数字和破折号,并且不能超过 40 个字符。", + "schemas.schemaNameValidationMessage": "名称只能包含字母、数字、破折号和空格。", + "schemas.schemaTagsHint": "用于注释自动化流程Schemas的标签。", + "schemas.searchPlaceholder": "搜索Schemas...", + "schemas.showFieldFailed": "显示字段失败。请重新加载。", + "schemas.synchronized": "Schema synchronized successfully.", + "schemas.synchronizeFailed": "同步Schemas失败。请重新加载。", + "schemas.tabFields": "字段", + "schemas.tabJson": "Json", + "schemas.tabMore": "More", + "schemas.tabScripts": "Scripts", + "schemas.tabUI": "UI", + "schemas.ui": "指定的字段", + "schemas.ui.unassignedFields": "未分配的字段", + "schemas.unpublished": "Unpublished", + "schemas.unpublishFailed": "无法取消发布Schemas。请重新加载。", + "schemas.updateFailed": "更新Schemas失败。请重新加载。", + "schemas.updateFieldFailed": "更新字段失败。请重新加载。", + "schemas.updatePreviewUrlsFailed": "无法配置预览网址。请重新加载。", + "schemas.updateRulesFailed": "更新Schemas规则失败。请重新加载。", + "schemas.updateScriptsFailed": "更新Schemas脚本失败。请重新加载。", + "schemas.updateUIFieldsFailed": "无法更新 UI 字段。请重新加载。", + "schemas.validateOnPublish": "发布时验证", + "search.addFilter": "添加过滤器", + "search.addGroup": "添加组", + "search.addSorting": "添加排序", + "search.advancedTour": "单击此图标可显示高级搜索菜单!", + "search.customQuery": "自定义查询", + "search.fullTextTour": "使用全文搜索在所有领域和语言中搜索内容!", + "search.help": "在 [文档](https://docs.squidex.io/04-guides/02-api.html) 中阅读有关过滤的更多信息。", + "search.myQueries": "我的查询", + "search.nameQuery": "命名查询", + "search.queriesEmpty": "搜索 {types} 并在搜索表单中使用 图标来保存所有贡献者的查询。", + "search.queryAllNewestFirst": "全部(最新的在前)", + "search.queryAllOldestFirst": "所有(最旧的在前)", + "search.quickNavPlaceholder": "快速导航(按'q')", + "search.saveQueryMyself": "只为我保存查询。", + "search.searchFailed": "搜索失败,请重新加载。", + "search.sharedQueries": "共享查询", + "search.sorting": "排序", + "start.login": "登录 Squidex", + "start.loginHint": "登录按钮将打开一个新的弹出窗口。一旦您登录成功,我们会将您重定向到 Squidex 管理门户。", + "start.madeBy": "自豪地制作", + "start.madeByCopyright": "Sebastian Stehle 和贡献者,2016-2021", + "tour.joinForum": "加入我们的论坛", + "tour.joinGithub": "加入我们的 Github", + "tour.skip": "跳过游览", + "tour.step0Next": "我们看看周围", + "tour.step0Text": "您可以立即开始管理和分发您的内容,但我们想先向您介绍一些基础知识...\n\n如何", + "tour.step1Next": "继续", + "tour.step1Text": "应用程序是您项目的存储库,例如(博客、网上商店或移动应用程序)。您可以为您的应用程序分配贡献者以协同工作。\n\n您可以在其中创建无限数量的应用程序Squidex 同时管理多个项目。", + "tour.step2Next": "继续!", + "tour.step2Text": "Schemas定义内容的结构、内容项的字段和数据类型。\n\n在向Schemas添加内容之前,请确保点击顶部的“发布”按钮使Schemas可用于您的内容 appSettings.editors。", + "tour.step3Next": "快到了!", + "tour.step3Text": "内容是您的应用程序中按Schemas分组的实际数据。\n\n首先选择一个已发布的Schemas,然后为此Schemas添加内容。", + "tour.step4Next": "知道了!", + "tour.step4Text": "资源包含所有也可以链接到您的内容的文件。例如图像、视频或文档。\n\n您可以在此处上传资源供以后使用,也可以在创建时直接上传带有资源字段的新内容项。", + "tour.step5Text": "但这还不是我们可以提供的全部支持。\n\n您可以访问 https://docs.squidex.io/> 阅读更多信息。\n\n您想加入我们的社区吗? ?", + "tour.step5Title": "太棒了,现在你知道基础了!", + "tour.tooltipConfirm": "知道了", + "tour.tooltipStop": "停止游览", + "tour.welcome": "欢迎来到", + "tour.welcomeProduct": "Squidex CMS", + "translate.translateFailed": "无法翻译文本。请重新加载。", + "usages.loadCallsFailed": "加载调用使用失败。请重新加载。", + "usages.loadMonthlyCallsFailed": "未能加载每月 API 调用。请重新加载。", + "usages.loadStorageFailed": "加载存储使用失败。请重新加载。", + "usages.loadTodayStorageFailed": "无法加载今天的存储大小。请重新加载。", + "users.create": "新建", + "users.createFailed": "创建用户失败,请重新加载。", + "users.createPageTitle": "创建用户", + "users.createTitle": "新用户", + "users.createTooltip": "新用户 (CTRL + N)", + "users.deleteConfirmText": "你真的要删除这个用户吗?", + "users.deleteConfirmTitle": "删除用户", + "users.deleteFailed": "删除用户失败,请重新加载。", + "users.editPageTitle": "编辑用户", + "users.editTitle": "编辑用户", + "users.listPageTitle": "用户管理", + "users.listTitle": "用户", + "users.loadFailed": "加载用户失败,请重新加载。", + "users.loadUserFailed": "加载用户失败。请重新加载。", + "users.lockFailed": "锁定用户失败。请重新加载。", + "users.lockTooltip": "锁定用户", + "users.passwordConfirmValidationMessage": "密码必须相同。", + "users.refreshTooltip": "刷新用户 (CTRL + SHIFT + R)", + "users.reloaded": "用户重新加载。", + "users.search": "搜索用户", + "users.unlockFailed": "解锁用户失败。请重新加载。", + "users.unlockTooltip": "解锁用户", + "users.updateFailed": "更新用户失败。请重新加载。", + "validation.between": "{field} 必须介于 '{min}' 和 '{max}' 之间。", + "validation.betweenlength": "{field|upper} 必须介于 {minlength} 和 {maxlength} 项之间。", + "validation.betweenlengthstring": "{field|upper} 必须介于 {minlength} 和 {maxlength} 个字符之间。", + "validation.email": "{field|upper} 必须是电子邮件地址。", + "validation.exactly": "{field|upper} 必须正好是 '{expected}'。", + "validation.exactlylength": "{field|upper} 必须正好有 {expected} 项。", + "validation.exactlylengthstring": "{field|upper} 必须正好有 {expected} 个字符。", + "validation.match": "{message}", + "validation.max": "{field|upper} 必须小于或等于 '{max}'。", + "validation.maxlength": "{field|upper} 不能超过 {requiredlength} 个项目。", + "validation.maxlengthstring": "{field|upper} 不能超过 {requiredlength} 个字符。", + "validation.min": "{field|upper} 必须大于或等于 '{min}'。", + "validation.minlength": "{field|upper} 必须至少有 {requiredlength} 项。", + "validation.minlengthstring": "{field|upper} 必须至少有 {requiredlength} 个字符。", + "validation.notAnValidSvg": "The SVG is malicious and contains script tags.", + "validation.pattern": "{field|upper} 与模式不匹配。", + "validation.patternmessage": "{message}", + "validation.required": "{field|upper} 是必需的。", + "validation.requiredTrue": "{field|upper} 是必需的。", + "validation.uniquestrings": "{field|upper} 不得包含重复值。", + "validation.validarrayvalues": "{field|upper} 包含无效值:{invalidvalue}。", + "validation.validdatetime": "{field|upper} 不是有效的日期时间。", + "validation.validvalues": "{field|upper} 不是一个有效值。", + "workflows.add": "添加工作流", + "workflows.addStep": "添加步骤", + "workflows.createFailed": "创建工作流失败。请重新加载。", + "workflows.deleteConfirmText": "你真的要删除工作流吗?", + "workflows.deleteConfirmTitle": "删除工作流", + "workflows.deleteFailed": "删除工作流失败。请重新加载。", + "workflows.empty": "尚未创建工作流。", + "workflows.loadFailed": "加载工作流失败。请重新加载。", + "workflows.notNamed": "未命名的工作流程", + "workflows.preventUpdates": "防止更新", + "workflows.publishedNotRemovable": "无法删除", + "workflows.refreshTooltip": "刷新工作流 (CTRL + SHIFT + R)", + "workflows.reloaded": "工作流已重新加载。", + "workflows.saved": "工作流已保存。", + "workflows.schemasHint": "将此工作流限制为特定Schemas或将其保留为所有Schemas的空。", + "workflows.syntax.expression": "表达式", + "workflows.syntax.for": "for", + "workflows.syntax.when": "when", + "workflows.tabEdit": "编辑", + "workflows.tabVisualize": "可视化", + "workflows.updateFailed": "无法更新工作流。请重新加载。", + "workflows.workflowNameHint": "工作流的可选名称。", + "workflows.workflowNamePlaceholder": "输入工作流名称" +} \ No newline at end of file diff --git a/backend/i18n/source/backend_it.json b/backend/i18n/source/backend_it.json index 79cf41986..e8a7f0942 100644 --- a/backend/i18n/source/backend_it.json +++ b/backend/i18n/source/backend_it.json @@ -115,7 +115,6 @@ "common.success": "Successo", "common.text": "Testo", "common.trigger": "Trigger", - "common.url": "URL", "common.warning": "Warning", "common.workflow": "Workflow", "common.workflowStep": "Step", @@ -171,7 +170,6 @@ "contents.validation.normalCharactersBetween": "Deve essere un testo tra {min} e {max} carattere(i).", "contents.validation.notAllowed": "Non è un valore consentito.", "contents.validation.pattern": "Deve seguire il pattern.", - "contents.validation.reference": "La geolocalizzazione può avere come campi solamente come latitudine e longitudine.", "contents.validation.referenceNotFound": "Contiene un collegamento '{id}' non valido.", "contents.validation.referenceToInvalidSchema": "Contiene dei collegamenti '{id}' ad uno schema errato.", "contents.validation.regexTooSlow": "La regular expression è troppo lenta.", diff --git a/backend/i18n/source/backend_nl.json b/backend/i18n/source/backend_nl.json index 722021f95..39161207f 100644 --- a/backend/i18n/source/backend_nl.json +++ b/backend/i18n/source/backend_nl.json @@ -112,7 +112,6 @@ "common.signup": "Aanmelden", "common.text": "Tekst", "common.trigger": "Trigger", - "common.url": "URL", "common.workflow": "Workflow", "common.workflowStep": "Stap", "common.workflowTransition": "Overgang", diff --git a/backend/i18n/source/backend_zh.json b/backend/i18n/source/backend_zh.json new file mode 100644 index 000000000..34740fe15 --- /dev/null +++ b/backend/i18n/source/backend_zh.json @@ -0,0 +1,374 @@ +{ + "annotations_AbsoluteUrl": "字段 '{name|lower}' 必须是绝对 URL。", + "annotations_Compare": "字段 '{name|lower}' 必须与 {other|lower} 相同。", + "annotations_EmailAddress": "字段'{name|lower}' 不是有效的电子邮件地址。", + "annotations_Range": "字段'{name|lower}' 必须介于 {min} 和 {max} 之间。", + "annotations_RegularExpression": "字段 '{name|lower}' 无法通过表达式验证", + "annotations_Required": "字段'{name|lower}' 是必需的。", + "annotations_StringLength": "字段'{name|lower}' 必须是最大长度为 {max} 的字符串。", + "annotations_StringLengthMinimum": "字段'{name|lower}' 必须是一个字符串,最小长度为 {min},最大长度为 {max}。", + "apps.clients.idAlreadyExists": "已存在具有相同 ID 的客户端。", + "apps.contributors.cannotChangeYourself": "你不能改变你自己的角色。", + "apps.contributors.maxReached": "您已达到计划的最大贡献者数量。", + "apps.contributors.onlyOneOwner": "无法删除唯一的所有者。", + "apps.languages.fallbackNotFound": "应用没有后备语言'{fallback}'。", + "apps.languages.languageAlreadyAdded": "语言已经添加。", + "apps.languages.masterLanguageNoFallbacks": "主语言不能有后备语言。", + "apps.languages.masterLanguageNotOptional": "主语言不能是可选的。", + "apps.languages.masterLanguageNotRemovable": "无法删除主语言。", + "apps.nameAlreadyExists": "同名应用已经存在。", + "apps.notImage": "文件不是图像", + "apps.plans.notFound": "不存在具有此 ID 的计划。", + "apps.plans.notPlanOwner": "计划只能由最初配置计划的用户更改。", + "apps.roles.defaultRoleNotRemovable": "无法删除默认角色。", + "apps.roles.defaultRoleNotUpdateable": "无法更新默认角色。", + "apps.roles.nameAlreadyExists": "已存在同名角色。", + "apps.roles.usedRoleByClientsNotRemovable": "分配客户端后无法删除角色。", + "apps.roles.usedRoleByContributorsNotRemovable": "当分配了贡献者时无法删除角色。", + "assets.folderNotFound": "资源文件夹不存在。", + "assets.folderRecursion": "无法将文件夹添加到自己的子文件夹中。", + "assets.maxSizeReached": "您已达到最大资源大小。", + "assets.referenced": "资源被内容引用,无法删除。", + "backups.alreadyRunning": "另一个备份进程已经在运行。", + "backups.maxReached": "您不能拥有超过 {max} 个备份。", + "backups.restoreRunning": "还原操作已经在运行。", + "comments.noPermissions": "您只能访问您的通知。", + "comments.notUserComment": "评论是由另一个用户创建的。", + "common.action": "动作", + "common.aspectHeight": "纵横高度", + "common.aspectWidth": "纵横宽", + "common.calculatedDefaultValue": "计算的默认值", + "common.clientd": "客户端 ID", + "common.clientId": "客户端 ID", + "common.clientSecret": "客户端密码", + "common.contentType": "内容类型", + "common.contributorId": "贡献者 ID 或电子邮件", + "common.critical": "关键", + "common.data": "数据", + "common.defaultValue": "默认值", + "common.displayName": "显示名称", + "common.editor": "编辑器", + "common.email": "电子邮件", + "common.errorNoPermission": "您没有必要的权限。", + "common.field": "字段", + "common.fieldIds": "字段 ID", + "common.fieldName": "字段名", + "common.file": "文件", + "common.folderName": "文件夹名称", + "common.fullTextNotSupported": "不支持查询搜索子句。", + "common.httpContentTypeNotDefined": "文件内容类型未定义。", + "common.httpFileNameNotDefined": "文件名未定义。", + "common.httpInvalidRequest": "模型无效。", + "common.httpInvalidRequestFormat": "请求正文的格式无效。", + "common.httpOnlyAsUser": "客户端不允许。", + "common.httpValidationError": "验证错误", + "common.initialStep": "初始步骤", + "common.jsError": "执行脚本失败,Javascript 错误:{message}", + "common.jsNotAllowed": "脚本禁止操作。", + "common.jsParseError": "无法执行脚本,Javascript 语法错误:{message}", + "common.jsRejected": "脚本拒绝了操作。", + "common.language": "语言代码", + "common.login": "登录", + "common.logout": "注销", + "common.maxCharacters": "最大字符数", + "common.maxHeight": "最大高度", + "common.maxItems": "最大项目数", + "common.maxLength": "最大长度", + "common.maxSize": "最大尺寸", + "common.maxValue": "最大值", + "common.maxWidth": "最大宽度", + "common.maxWords": "最大字数", + "common.minCharacters": "最小字符数", + "common.minHeight": "最小高度", + "common.minItems": "最小项目", + "common.minLength": "最小长度", + "common.minSize": "最小尺寸", + "common.minValue": "最小值", + "common.minWidth": "最小宽度", + "common.minWords": "最小词", + "common.name": "名称", + "common.notFoundValue": "- 未找到 -", + "common.numDays": "天数", + "common.odataFailure": "无法解析查询:{message},查询:{odata}", + "common.odataFilterNotValid": "OData $filter 子句无效:{message}", + "common.odataNotSupported": "查询不支持 OData 操作:{odata}", + "common.odataSearchNotValid": "OData $search 子句无效:{message}", + "common.oldPassword": "旧密码", + "common.other": "其他", + "common.partitioning": "分区", + "common.password": "密码", + "common.passwordConfirm": "确认", + "common.pattern": "模式", + "common.permissions": "权限", + "common.planId": "计划 ID", + "common.previewUrls": "预览网址", + "common.product": "Squidex Headless CMS", + "common.properties": "属性", + "common.property": "属性", + "common.readonlyMode": "应用程序目前处于只读模式。", + "common.remove": "删除", + "common.resultTooLarge": "结果集太大无法检索。使用 $take 参数减少项目数。", + "common.role": "角色", + "common.save": "保存", + "common.schemaId": "Schemas ID", + "common.signup": "注册", + "common.success": "成功", + "common.text": "文本", + "common.trigger": "触发器", + "common.warning": "警告", + "common.workflow": "工作流程", + "common.workflowStep": "步骤", + "common.workflowTransition": "过渡", + "contents.bulkInsertQueryNotUnique": "多个内容与查询匹配。", + "contents.draftNotCreateForUnpublished": "您只能在内容发布后创建新版本。", + "contents.draftToDeleteNotFound": "没有要删除的内容。", + "contents.invalidArrayOfObjects": "无效的 json 类型,预期的对象数组。", + "contents.invalidArrayOfStrings": "无效的 json 类型,需要字符串数组。", + "contents.invalidBoolean": "无效的 json 类型,应为布尔值。", + "contents.invalidComponentNoObject": "无效的 json 对象,带有 'schemaId' 字段的预期对象。", + "contents.invalidComponentNoType": "无效组件。未找到 'schemaId' 字段。", + "contents.invalidComponentUnknownSchema": "无效组件。找不到Schemas。", + "contents.invalidGeolocation": "无效的 json 类型,预期的纬度/经度对象。", + "contents.invalidGeolocationLatitude": "纬度必须在 -90 到 90 之间。", + "contents.invalidGeolocationLongitude": "经度必须在 -180 到 180 之间。", + "contents.invalidNumber": "无效的 json 类型,需要的数字。", + "contents.invalidString": "无效的 json 类型,需要的字符串。", + "contents.listReferences": "{count} 个引用", + "contents.referenced": "内容被其他内容引用,无法删除或取消发布。", + "contents.schemaNotPublished": "Schemas未发布。", + "contents.singletonNotChangeable": "无法更新单个内容。", + "contents.singletonNotCreatable": "无法创建单个内容。", + "contents.singletonNotDeletable": "无法删除单个内容。", + "contents.statusNotValid": "工作流中未定义状态。", + "contents.statusTransitionNotAllowed": "无法将状态从 {oldStatus} 更改为 {newStatus}。", + "contents.validation.aspectRatio": "必须有纵横比 {width}:{height}。", + "contents.validation.assetNotFound": "未找到 ID {id}。", + "contents.validation.between": "必须介于 {min} 和 {max} 之间。", + "contents.validation.characterCount": "必须正好有 {count} 个字符。", + "contents.validation.charactersBetween": "必须在 {min} 到 {max} 个字符之间。", + "contents.validation.duplicates": "不得包含重复值。", + "contents.validation.error": "验证失败,内部错误。", + "contents.validation.exactValue": "必须正好是 {value}。", + "contents.validation.extension": "必须是允许的扩展名。", + "contents.validation.image": "不是图片。", + "contents.validation.invalid": "无效值。", + "contents.validation.itemCount": "必须正好有 {count} 个项目。", + "contents.validation.itemCountBetween": "必须介于 {min} 和 {max} 项之间。", + "contents.validation.max": "必须小于或等于 {max}。", + "contents.validation.maxCharacters": "不得超过 {max} 个文本字符。", + "contents.validation.maximumHeight": "高度 {height}px 必须小于 {max}px。", + "contents.validation.maximumSize": "{size} 的大小必须小于 {max}。", + "contents.validation.maximumWidth": "宽度 {width}px 必须小于 {max}px。", + "contents.validation.maxItems": "不得超过 {max} 个项目。", + "contents.validation.maxLength": "不得超过 {max} 个字符。", + "contents.validation.maxWords": "不得超过 {max} 个单词。", + "contents.validation.min": "必须大于或等于 {min}。", + "contents.validation.minimumHeight": "高度 {height}px 必须大于 {min}px。", + "contents.validation.minimumSize": "{size} 的大小必须大于 {min}。", + "contents.validation.minimumWidth": "宽度 {width}px 必须大于 {min}px。", + "contents.validation.minItems": "必须至少有 {min} 个项目。", + "contents.validation.minLength": "必须至少有 {min} 个字符。", + "contents.validation.minNormalCharacters": "必须至少有 {min} 个文本字符。", + "contents.validation.minWords": "必须至少有 {min} 个单词。", + "contents.validation.mustBeEmpty": "不得定义值。", + "contents.validation.normalCharacterCount": "必须正好有 {count} 个文本字符。", + "contents.validation.normalCharactersBetween": "必须在 {min} 和 {max} 个文本字符之间。", + "contents.validation.notAllowed": "不允许的值。", + "contents.validation.pattern": "必须遵循模式。", + "contents.validation.referenceNotFound": "未找到引用 '{id}'。", + "contents.validation.referenceToInvalidSchema": "参考 '{id}' 的Schemas无效。", + "contents.validation.regexTooSlow": "正则表达式太慢了。", + "contents.validation.required": "必填字段。", + "contents.validation.unique": "存在另一个具有相同值的内容。", + "contents.validation.unknownField": "不是已知的 {fieldType}。", + "contents.validation.wordCount": "必须正好有 {count} 个单词。", + "contents.validation.wordsBetween": "必须在 {min} 到 {max} 个单词之间。", + "contents.workflowErrorUpdate": "工作流不允许更新状态为 {status}", + "dotnet_identity_DefaultEror": "发生未知故障。", + "dotnet_identity_DuplicateEmail": "电子邮件已被占用。", + "dotnet_identity_DuplicateUserName": "用户名已被占用。", + "dotnet_identity_InvalidEmail": "电子邮件无效。", + "dotnet_identity_InvalidUserName": "用户名“{0}”无效,只能包含字母或数字。", + "dotnet_identity_LoginAlreadyAssociated": "已存在使用此登录名的用户。", + "dotnet_identity_PasswordMismatch": "密码不正确。", + "dotnet_identity_PasswordRequiresDigit": "密码必须至少有一位 ('0'-'9')。", + "dotnet_identity_PasswordRequiresLower": "密码必须至少有一个小写字母 ('a'-'z')。", + "dotnet_identity_PasswordRequiresNonAlphanumeric": "密码必须至少有一个非字母数字字符。", + "dotnet_identity_PasswordRequiresUniqueChars": "密码必须至少使用 {0} 个不同的字符。", + "dotnet_identity_PasswordRequiresUpper": "密码必须至少有一个大写字母 ('A'-'Z')。", + "dotnet_identity_PasswordTooShort": "密码太短。", + "dotnet_identity_PwnedError": "此密码以前曾出现在数据泄露事件中,永远不应使用。如果您以前曾在任何地方使用过它,请更改它!", + "exception.invalidJsonQuery": "Json 查询无效:{message}", + "exception.invalidJsonQueryJson": "Json 查询无效 json: {message}", + "exceptions.domainObjectConflict": "实体 ({id}) 已经存在。", + "exceptions.domainObjectDeleted": "实体 ({id}) 已被删除。", + "exceptions.domainObjectNotFound": "实体 ({id}) 不存在。", + "exceptions.domainObjectVersion": "实体 ({id}) 请求版本 {expectedVersion},但找到 {currentVersion}。", + "history.apps.clientAdded": "将客户端 {[Id]} 添加到应用程序", + "history.apps.clientRevoked": "已撤销客户端 {[Id]}", + "history.apps.clientUpdated": "更新的客户端 {[Id]}", + "history.apps.contributoreAssigned": "已分配 {user:[Contributor]} 作为 {[Role]}", + "history.apps.contributoreRemoved": "从应用中删除了 {user:[Contributor]}", + "history.apps.languagedAdded": "添加语言 {[Language]}", + "history.apps.languagedRemoved": "已删除语言 {[Language]}", + "history.apps.languagedSetToMaster": "将主语言更改为 {[Language]}", + "history.apps.languagedUpdated": "更新的语言 {[Language]}", + "history.apps.planChanged": "已将计划更改为 {[Plan]}", + "history.apps.planReset": "重置计划", + "history.apps.roleAdded": "添加角色 {[Name]}", + "history.apps.roleDeleted": "已删除角色 {[Name]}", + "history.apps.roleUpdated": "更新角色 {[Name]}", + "history.apps.settingsUpdated": "更新的 UI 设置", + "history.assets.replaced": "替换的资源。", + "history.assets.updated": "更新的资源。", + "history.assets.uploaded": "上传的资源。", + "history.contents.created": "创建了 {[Schema]} 内容。", + "history.contents.deleted": "删除了 {[Schema]} 内容。", + "history.contents.draftCreated": "创建了新草稿。", + "history.contents.draftDeleted": "已删除的草稿。", + "history.contents.scheduleCompleted": "已安排将 {[Schema]} 内容的状态更改为 {[Status]}。", + "history.contents.scheduleFailed": "无法安排 {[Schema]} 内容的状态更改。", + "history.contents.updated": "更新了 {[Schema]} 内容。", + "history.schemas.created": "创建的Schemas {[Name]}。", + "history.schemas.deleted": "已删除Schemas {[Name]}。", + "history.schemas.fieldAdded": "将字段 {[Field]} 添加到Schemas {[Name]}。", + "history.schemas.fieldDeleted": "已从Schemas {[Name]} 中删除字段 {[Field]}。", + "history.schemas.fieldDisabled": "Schemas {[Name]} 的禁用字段 {[Field]}。", + "history.schemas.fieldHidden": "有Schemas {[Name]} 的隐藏字段 {[Field]}。", + "history.schemas.fieldLocked": "已锁定Schemas {[Name]} 的字段 {[Field]}。", + "history.schemas.fieldShown": "已显示Schemas {[Name]} 的字段 {[Field]}。", + "history.schemas.fieldsReordered": "Schemas {[Name]} 的重新排序字段。", + "history.schemas.fieldUpdated": "已更新Schemas {[Name]} 的字段 {[Field]}。", + "history.schemas.published": "已发布的Schemas {[Name]}。", + "history.schemas.scriptsConfigured": "Schemas {[Name]} 的配置脚本。", + "history.schemas.unpublished": "未发布的Schemas {[Name]}。", + "history.schemas.updated": "更新的Schemas {[Name]}。", + "history.statusChanged": "已将 {[Schema]} 内容的状态更改为 {[Status]}。", + "login.githubPrivateEmail": "您的邮箱在 Github 中设置为私有。请设置为公开以使用 Github 登录。", + "rules.ruleAlreadyRunning": "另一个规则已经在运行。", + "schema.fieldIsLocked": "Schemas字段被锁定。", + "schema.fieldNotInSchema": "字段不是Schemas的一部分。", + "schema.noPermission": "您没有此Schemas的权限。", + "schema.notFoundId": "Schemas {id} 不存在。", + "schemas.dateTimeCalculatedDefaultAndDefaultError": "计算出的默认值和默认值不能一起使用。", + "schemas.duplicateFieldName": "字段 '{field}' 已添加两次。", + "schemas.fieldCannotBeUIField": "字段不能是 UI 字段。", + "schemas.fieldNameAlreadyExists": "已存在同名字段。", + "schemas.fieldsNotCovered": "字段 ID 未涵盖所有字段。", + "schemas.nameAlreadyExists": "一个同名的Schemas已经存在。", + "schemas.number.inlineEditorError": "无线电编辑器不允许内联编辑。", + "schemas.onlyArraysHaveNested": "只有数组字段可以有嵌套字段。", + "schemas.onylArraysInRoot": "嵌套字段不能是数组字段。", + "schemas.references.resolveError": "只有在 MaxItems 为 1 时才能解析引用。", + "schemas.string.inlineEditorError": "内联编辑只允许用于下拉菜单、slug 和输入字段。", + "schemas.stringEditorsNeedAllowedValuesError": "单选按钮或下拉列表需要允许的值。", + "schemas.tags.editorNeedsAllowedValues": "复选框或下拉列表需要允许的值。", + "schemas.uiFieldCannotBeDisabled": "UI 字段不能被禁用。", + "schemas.uiFieldCannotBeEnabled": "UI 字段无法启用。", + "schemas.uiFieldCannotBeHidden": "UI 字段不能隐藏。", + "schemas.uiFieldCannotBeShown": "UI 字段无法显示。", + "search.contentResult": "{name} 内容", + "search.contentsResult": "{name} 内容", + "search.schemaResult": "{name} Schemas", + "security.passwordStolen": "此密码以前曾出现在数据泄露事件中,永远不应使用。如果您以前曾在任何地方使用过它,请更改它!", + "setup.createUser.button": "创建用户", + "setup.createUser.confirmPassword": "确认", + "setup.createUser.failure": "既未配置密码认证,也未配置 Google 等外部认证提供商。请检查您的设置和日志。", + "setup.createUser.headline": "管理员用户", + "setup.createUser.headlineCreate": "创建管理员用户", + "setup.createUser.loginHint": "您至少配置了一个外部身份验证提供程序,例如 Google。只需转到登录页面并登录即可成为管理员。", + "setup.createUser.loginLink": "进入登录页面。", + "setup.createUser.separator": "OR", + "setup.headline": "安装", + "setup.hint": "您看到此屏幕是因为尚无用户存在。创建用户后,您将无法再次使用此屏幕。", + "setup.madeBy": "制作", + "setup.madeByCopyright": "Sebastian Stehle 和贡献者,2016-2021", + "setup.ruleAppCreation.warningAdmins": "通过你的设置,只有管理员可以创建新的应用程序。如果你想改变这个设置 UI__ONLYADMINCANCREATEAPPS=false 作为环境变量。", + "setup.ruleAppCreation.warningAll": "通过你的设置,每个用户都可以创建新的应用程序。如果你想改变这个设置 UI__ONLYADMINCANCREATEAPPS=true 作为环境变量。", + "setup.ruleFolder.warning": "您正在使用文件夹资源存储,其中所有资源都存储在文件系统中。请记住将资源文件夹包含在您的备份策略中并将其映射到卷, 如果您使用的是 Docker。", + "setup.ruleFtp.warning": "您正在使用 FTP 资源存储。由于性能不佳,不建议使用这种存储类型。", + "setup.ruleHttps.failure": " 您不是通过 https 访问站点。如果此警告不正确,则 Squidex 无法检测 https 模式,因为您的实例位于反向代理(例如 nginx)之后。确保正确转发 http 标头, 通过 X-Forwarded-* 标头。", + "setup.ruleHttps.success": "恭喜您,您正在通过安全连接 (https) 访问 Squidex 安装。", + "setup.rules.headline": "系统清单", + "setup.ruleUrl.failure": "您应该仅通过一个规范 URL 访问 Squidex,并通过 URLS__BASEURL 环境变量配置此 URL。当前的基本 URL {actual}与基本 url {configured} 不匹配。", + "setup.ruleUrl.success": "恭喜 URLS__BASEURL 环境变量配置正确。", + "setup.title": "安装", + "users.accessDenied.text": "不允许此操作,您的帐户可能被锁定。", + "users.accessDenied.title": "拒绝访问", + "users.consent.agree": "我同意!", + "users.consent.cookiesHeadline": "Cookies & Analytics", + "users.consent.cookiesText": "

我理解并同意 Squidex 使用 cookie 来确保您在我们的平台上获得最佳体验并存储您的登录状态。

我理解并同意 Squidex 已集成 Google Analytics(具有匿名器功能)。Google Analytics 是一项网络分析服务,用于收集和分析有关用户行为的数据。

我接受 隐私政策.

", + "users.consent.emailHeadline": "自动电子邮件(可选)", + "users.consent.emailText": "我理解并同意 Squidex 发送电子邮件来通知我有关新功能、重大更改和停机时间的信息。", + "users.consent.headline": "我们需要您的同意", + "users.consent.needed": "你必须同意。", + "users.consent.piiHeadline": "个人信息", + "users.consent.piiText": "我理解并同意 Squidex 收集从外部身份验证提供商(例如 Google、Microsoft 或 Github)检索到的以下私人信息。
  • 向所有其他用户提供基本的个人信息(名字、姓氏和照片),以便他们可以将您添加到他们的工作空间。
  • 您可以随时选择更改这些信息以使您的帐户匿名.
  • 您的用户帐户具有唯一标识符,对于我们跟踪的所有更改,您进行了这些更改并将此信息提供给其他用户。
", + "users.consent.title": "同意", + "users.deleteYourselfError": "你不能删除自己。", + "users.error.headline": "操作失败", + "users.error.text": "我们真的很抱歉出现问题。", + "users.error.title": "错误", + "users.errorHappened": "发生意外异常。", + "users.lockedOutText": "您的账户已被锁定,请联系管理员。", + "users.lockedOutTitle": "帐户已锁定", + "users.lockYourselfError": "你不能锁定自己。", + "users.login.askAdmin": "", + "users.login.emailPlaceholder": "输入邮箱", + "users.login.error": "电子邮件或密码不正确", + "users.login.loginWith": "{action} with {provider}", + "users.login.noAccountLoginAction": "点击此处登录", + "users.login.noAccountLoginQuestion": "已经注册?", + "users.login.noAccountSignupAction": "点击此处注册", + "users.login.noAccountSignupQuestion": "还没有账号?", + "users.login.passwordPlaceholder": "输入密码", + "users.login.separator": "或", + "users.logout.headline": "登出!", + "users.logout.text": "!请关闭这个弹窗。", + "users.logout.title": "注销", + "users.profile.addLoginDone": "登录添加成功。", + "users.profile.changePassword": "更改密码", + "users.profile.changePasswordDone": "密码更改成功。", + "users.profile.clientHint": "使用客户端凭据通过您的个人资料信息和权限访问 API", + "users.profile.clientTitle": "客户端", + "users.profile.confirmPassword": "确认", + "users.profile.generateClient": "生成", + "users.profile.generateClientDone": "客户端密码生成成功。", + "users.profile.headline": "编辑个人资料", + "users.profile.hideProfile": "不要向其他用户显示我的个人资料", + "users.profile.loginsTitle": "登录", + "users.profile.passwordTitle": "密码", + "users.profile.pii": "个人信息", + "users.profile.propertiesHint": "为规则和脚本使用自定义属性。", + "users.profile.propertiesTitle": "属性", + "users.profile.propertyAdd": "添加属性", + "users.profile.removeLoginDone": "登录提供程序删除成功。", + "users.profile.setPassword": "设置密码", + "users.profile.setPasswordDone": "密码设置成功。", + "users.profile.title": "个人资料", + "users.profile.updateProfileDone": "账户更新成功。", + "users.profile.updatePropertiesDone": "账户更新成功。", + "users.profile.uploadPicture": "上传图片", + "users.profile.uploadPictureDone": "图片上传成功。", + "users.unlockYourselfError": "您无法解锁自己。", + "users.userLocked": "不允许用户登录。", + "users.userNotFound": "找不到用户。", + "validation.between": "{property|upper} 必须介于 {min} 和 {max} 之间。", + "validation.greaterEqualsThan": "{property|upper} 必须大于或等于 {other|lower}。", + "validation.greaterThan": "{property|upper} 必须大于 {other|lower}。", + "validation.javascriptProperty": "{property|upper} 不是 Javascript 属性名称。", + "validation.lessEqualsThan": "{property|upper} 必须小于或等于 {other|lower}。", + "validation.lessThan": "{property|upper} 必须小于 {other|lower}。", + "validation.notAnImage": "图片不是有效图片。", + "validation.onlyOneFile": "只能上传一个文件。", + "validation.required": "{property|upper} 是必需的。", + "validation.requiredBoth": "如果使用 {property1|lower} 或 {property2|lower},则必须定义两者。", + "validation.requiredValue": "必须定义值。", + "validation.slug": "{property|upper} 不是有效的 slug。", + "validation.valid": "{property|upper} 不是一个有效值。", + "workflows.overlap": "多个工作流覆盖所有Schemas。", + "workflows.publishedIsInitial": "初始步骤不能发布步骤。", + "workflows.publishedNotDefined": "工作流必须有一个已发布的步骤。", + "workflows.publishedStepNotFound": "转换的目标无效。", + "workflows.schemaOverlap": "Schemas'{schema}' 被多个工作流覆盖。" +} \ No newline at end of file diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json index a14b65bf0..359942ff1 100644 --- a/backend/i18n/source/frontend_en.json +++ b/backend/i18n/source/frontend_en.json @@ -336,10 +336,10 @@ "common.separateByLine": "Separate by line", "common.settings": "Settings", "common.sidebar": "Sidebar Extension", - "common.sidebarTour": "The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.", + "common.sidebarTour": "The sidebar navigation contains useful context specific links. Here you can view the history of how this schema has changed over time.", "common.skipped": "Skipped", "common.slug": "Slug", - "common.stars.max": "Must not have more more than 15 stars", + "common.stars.max": "Must not have more than 15 stars", "common.status": "Status", "common.statusChangeTo": "Change to", "common.submit": "Submit", @@ -416,7 +416,7 @@ "contents.loadContentFailed": "Failed to load content. Please reload.", "contents.loadDataFailed": "Failed to load data. Please reload.", "contents.loadFailed": "Failed to load contents. Please reload.", - "contents.loadVersionFailed": "Failed to version a new version. Please reload.", + "contents.loadVersionFailed": "Failed to load a new version. Please reload.", "contents.localizedFieldDescription": "The '{fieldName}' field of the content item (localized).", "contents.newStatusFieldDescription": "The new status of the content item.", "contents.noReference": "- No Reference -", @@ -761,9 +761,11 @@ "schemas.fieldTypes.assets.sizeMin": "Max Size", "schemas.fieldTypes.boolean.description": "Yes or no, true or false.", "schemas.fieldTypes.component.description": "Embed another schema to this content.", - "schemas.fieldTypes.components.description": "Embed another schemas to this content as array.", + "schemas.fieldTypes.components.description": "Embed other schemas to this content as array.", "schemas.fieldTypes.dateTime.defaultMode": "Default Mode", "schemas.fieldTypes.dateTime.description": "Events date, opening hours.", + "schemas.fieldTypes.dateTime.format": "Pattern", + "schemas.fieldTypes.dateTime.formatHint": "The pattern when shown in the UI, see: https://date-fns.org/v2.22.1/docs/format", "schemas.fieldTypes.dateTime.rangeMax": "Max Value", "schemas.fieldTypes.dateTime.rangeMin": "Min Value", "schemas.fieldTypes.geolocation.description": "Coordinates: latitude and longitude.", @@ -777,6 +779,7 @@ "schemas.fieldTypes.references.countMin": "Min Items", "schemas.fieldTypes.references.description": "Links to other content items.", "schemas.fieldTypes.references.mustBePublished": "References must be published", + "schemas.fieldTypes.references.resolve": "Resolve references", "schemas.fieldTypes.references.resolveHint": "Show the name of the referenced item in content list when MaxItems is set to 1.", "schemas.fieldTypes.string.characters": "Characters", "schemas.fieldTypes.string.charactersMax": "Max Characters", @@ -941,6 +944,7 @@ "validation.min": "{field|upper} must be greater or equal to '{min}'.", "validation.minlength": "{field|upper} must have at least {requiredlength} item(s).", "validation.minlengthstring": "{field|upper} must have at least {requiredlength} character(s).", + "validation.notAnValidSvg": "The SVG is malicious and contains script tags.", "validation.pattern": "{field|upper} does not match to the pattern.", "validation.patternmessage": "{message}", "validation.required": "{field|upper} is required.", diff --git a/backend/i18n/source/frontend_it.json b/backend/i18n/source/frontend_it.json index 902d4767a..54b955dba 100644 --- a/backend/i18n/source/frontend_it.json +++ b/backend/i18n/source/frontend_it.json @@ -588,7 +588,6 @@ "roles.revokeFailed": "Non è stato possibile rimuovere il ruolo. Per favore ricarica.", "roles.roleNamePlaceholder": "Inserisci il nome del ruolo", "roles.updateFailed": "Non è stato possibile aggiornare il ruolo. Per favore ricarica.", - "rules.actionEdit": "Modifica l'Azione", "rules.cancelFailed": "Non è stato possibile eliminare la regola. Per favore ricarica.", "rules.create": "Crea un nuova Regola", "rules.createFailed": "Non è stato possibile creare una nuova regola. Per favore ricarica.", @@ -596,10 +595,8 @@ "rules.deleteConfirmText": "Sei sicuro di voler eliminare la regola?", "rules.deleteConfirmTitle": "Cancella la regola", "rules.deleteFailed": "Non è stato possibile eliminare la regola. Per favore ricarica.", - "rules.disableFailed": "Non è stato possibile disabilitare la regola. Per favore ricarica.", "rules.empty": "Nessuna regola è stato ancora creata.", "rules.emptyAddRule": "Aggiungi una regola", - "rules.enableFailed": "Non è stato possibile abilitare la regola. Per favore ricarica.", "rules.enqueued": "La regola è stata aggiunta alle code.", "rules.listPageTitle": "Regole", "rules.loadFailed": "Non è stato possibile caricare le regole. Per favore ricarica.", @@ -629,14 +626,9 @@ "rules.stop": "La regola si fermerà al più presto.", "rules.triggerConfirmText": "Sei sicuro che voler attivare la regola?", "rules.triggerConfirmTitle": "Attiva la regola", - "rules.triggerEdit": "Modifica l'Attivazione", "rules.triggerFailed": "Non è stato possibile attivare la regola. Per favore ricarica.", "rules.unnamed": "Regola senza nome", "rules.updateFailed": "Non è stato possibile aggiornare la regola. Per favore ricarica.", - "rules.wizard.actionHint": "La selezione del tipo di azione non potrà essere modificata successivamente.", - "rules.wizard.selectAction": "Seleziona l'Azione", - "rules.wizard.selectTrigger": "Seleziona l'Attivazione", - "rules.wizard.triggerHint": "La selezione del tipo di attivazione non potrà essere modificata successivamente.", "schemas.addField": "Aggiungi un Campo", "schemas.addFieldAndClose": "Crea e chiudi", "schemas.addFieldAndCreate": "Crea e aggiungi il campo", diff --git a/backend/i18n/source/frontend_nl.json b/backend/i18n/source/frontend_nl.json index 8a1b57ebf..00c21802c 100644 --- a/backend/i18n/source/frontend_nl.json +++ b/backend/i18n/source/frontend_nl.json @@ -560,7 +560,6 @@ "roles.revokeFailed": "Kan rol niet intrekken. Laad opnieuw.", "roles.roleNamePlaceholder": "Voer de rolnaam in", "roles.updateFailed": "Update rol mislukt. Laad opnieuw.", - "rules.actionEdit": "Bewerk actie", "rules.cancelFailed": "Annuleren van regel is mislukt. Laad opnieuw.", "rules.create": "Maak een nieuwe regel", "rules.createFailed": "Maken van regel is mislukt. Laad opnieuw.", @@ -568,10 +567,8 @@ "rules.deleteConfirmText": "Wil je de regel echt verwijderen?", "rules.deleteConfirmTitle": "Regel verwijderen", "rules.deleteFailed": "Verwijderen van regel is mislukt. Laad opnieuw.", - "rules.disableFailed": "Kan regel niet uitschakelen. Laad opnieuw.", "rules.empty": "Nog geen regel aangemaakt.", "rules.emptyAddRule": "Regel toevoegen", - "rules.enableFailed": "Kan regel niet inschakelen. Laad opnieuw.", "rules.enqueued": "Regel is toegevoegd aan de wachtrij.", "rules.listPageTitle": "Regels", "rules.loadFailed": "Laden van regels is mislukt. Laad opnieuw.", @@ -600,14 +597,9 @@ "rules.stop": "Regel stopt binnenkort.", "rules.triggerConfirmText": "Wil je echt de regel activeren?", "rules.triggerConfirmTitle": "Trigger regel", - "rules.triggerEdit": "Trigger bewerken", "rules.triggerFailed": "Kan regel niet activeren. Laad opnieuw.", "rules.unnamed": "Naamloos regel", "rules.updateFailed": "Update regel mislukt. Laad opnieuw.", - "rules.wizard.actionHint": "De selectie van het actietype kan later niet worden gewijzigd.", - "rules.wizard.selectAction": "Selecteer actie", - "rules.wizard.selectTrigger": "Selecteer Trigger", - "rules.wizard.triggerHint": "De selectie van het triggertype kan later niet worden gewijzigd.", "schemas.addField": "Veld toevoegen", "schemas.addFieldAndClose": "Maken en sluiten", "schemas.addFieldAndCreate": "Maak en voeg veld toe", diff --git a/backend/i18n/source/frontend_zh.json b/backend/i18n/source/frontend_zh.json new file mode 100644 index 000000000..139744df6 --- /dev/null +++ b/backend/i18n/source/frontend_zh.json @@ -0,0 +1,975 @@ +{ + "api.contentApi": "内容 API", + "api.generalApi": "通用 API", + "api.graphql": "GraphQL", + "api.graphqlPageTitle": "GraphQL", + "api.pageTitle": "API", + "api.title": "API", + "apps.allApps": "所有应用程序", + "apps.appLoadFailed": "加载应用失败。请重新加载。", + "apps.appNameHint": "您只能使用字母、数字和破折号,并且不能超过 40 个字符。", + "apps.appNameValidationMessage": "名称可以包含小写字母 (a-z)、数字和破折号。", + "apps.appNameWarning": "以后不能更改应用名称。", + "apps.appsButtonCreate": "应用概览", + "apps.appsButtonFallbackTitle": "应用概览", + "apps.archive": "存档应用", + "apps.archiveConfirmText": "你真的要存档这个应用程序吗?", + "apps.archiveConfirmTitle": "存档应用程序", + "apps.archiveFailed": "存档应用失败。请重新加载。", + "apps.archiveWarning": "一旦你归档了一个应用程序,就没有回头路了。请确定。", + "apps.create": "创建应用程序", + "apps.createBlankApp": "新应用程序", + "apps.createBlankAppDescription": "创建一个没有内容和Schemas的新空白应用程序。", + "apps.createBlogApp": "新博客示例", + "apps.createBlogAppDescription": "从我们准备使用的博客开始。", + "apps.createFailed": "创建应用失败。请重新加载。", + "apps.createProfileApp": "新配置文件示例", + "apps.createProfileAppDescription": "创建您的个人资料页面。", + "apps.createWithTemplate": "创建 {template} 示例", + "apps.empty": "您还没有与任何应用协作", + "apps.generalSettings": "通用", + "apps.generalSettingsDangerZone": "通用", + "apps.image": "图片", + "apps.imageDrop": "拖放上传", + "apps.leave": "离开应用程序", + "apps.leaveConfirmText": "你真的要离开这个应用程序吗?", + "apps.leaveConfirmTitle": "离开应用程序。", + "apps.leaveFailed": "退出应用失败,请重新加载。", + "apps.listPageTitle": "应用程序", + "apps.loadFailed": "加载应用失败。请重新加载。", + "apps.loadSettingsFailed": "更新界面设置失败。请重新加载。", + "apps.removeImage": "删除图片", + "apps.removeImageFailed": "删除应用图片失败。请重新加载。", + "apps.updateFailed": "更新应用失败。请重新加载。", + "apps.updateSettingsFailed": "更新界面设置失败。请重新加载。", + "apps.upgradeHintCurrent": "你在 {plan} 计划中。", + "apps.upgradeHintUpgrade": "升级!", + "apps.uploadImage": "拖放文件以替换应用图片。使用正方形大小。", + "apps.uploadImageButton": "上传文件", + "apps.uploadImageFailed": "上传图片失败,请重新加载。", + "apps.uploadImageTooBig": "应用图片太大。", + "apps.welcomeSubtitle": "欢迎来到 Squidex。", + "apps.welcomeTitle": "Hi {user}", + "appSettings.editors.deleteConfirmText": "你真的要删除这个编辑器 URL 吗?", + "appSettings.editors.deleteConfirmTitle": "删除编辑器 URL", + "appSettings.editors.empty": "尚未创建编辑器 URL。", + "appSettings.editors.title": "自定义编辑器", + "appSettings.hideScheduler": "隐藏预定发布对话框", + "appSettings.patterns.deleteConfirmText": "你真的要删除这个模式吗?", + "appSettings.patterns.deleteConfirmTitle": "删除模式", + "appSettings.patterns.empty": "尚未创建模式。", + "appSettings.patterns.title": "模式", + "appSettings.refreshTooltip": "刷新 UI 设置 (CTRL + SHIFT + R)", + "appSettings.reloaded": "UI 设置重新加载。", + "appSettings.title": "界面设置", + "assets.createFolder": "创建文件夹", + "assets.createFolderFailed": "资源文件夹创建失败,请重新加载。", + "assets.createFolderTooltip": "创建新文件夹 (CTRL + SHIFT + G)", + "assets.deleteConfirmText": "你真的要删除资源吗?", + "assets.deleteConfirmTitle": "删除资源", + "assets.deleteFailed": "资源删除失败,请重新加载。", + "assets.deleteFolderConfirmText": "您真的要删除文件夹和所有资源吗?", + "assets.deleteFolderConfirmTitle": "删除文件夹", + "assets.deleteMetadataConfirmText": "你真的要删除这个元数据吗?", + "assets.deleteMetadataConfirmTitle": "删除元数据", + "assets.deleteReferrerConfirmText": "资源被内容项引用。\n\n您真的要删除资源吗?", + "assets.deleteReferrerConfirmTitle": "删除资源", + "assets.downloadVersion": "下载此版本", + "assets.dropToUpdate": "下拉更新", + "assets.duplicateFile": "资源已经上传。", + "assets.editor.flipHorizo​​ntally": "水平翻转", + "assets.editor.flipVertically": "垂直翻转", + "assets.editor.focusPointLabel": "选择焦点位置", + "assets.editor.focusPointPreview": "不同尺寸的预览", + "assets.editor.rotateLeft": "向左旋转", + "assets.editor.rotateRight": "向右旋转", + "assets.fileTooBig": "资源太大。", + "assets.folderName": "文件夹名称", + "assets.folderNameHint": "文件夹名称用作显示名称,不能唯一。", + "assets.insertAssets": "插入资源", + "assets.linkSelected": "链接选定的资源 ({count})", + "assets.listPageTitle": "资源", + "assets.loadFailed": "资源加载失败,请重新加载。", + "assets.loadFoldersFailed": "加载资源文件夹失败。请重新加载。", + "assets.metadata": "元数据", + "assets.metadataAdd": "添加元数据", + "assets.moveFailed": "资源移动失败。请重新加载。", + "assets.protected": "受保护", + "assets.refreshTooltip": "刷新资源 (CTRL + SHIFT + R)", + "assets.reloaded": "资源重新加载。", + "assets.removeConfirmText": "你真的要移除资源吗?", + "assets.removeConfirmTitle": "移除资源", + "assets.renameFolder": "重命名文件夹", + "assets.replaceConfirmText": "你真的想用更新的版本替换资源吗", + "assets.replaceConfirmTitle": "替换资源?", + "assets.replaceFailed": "替换资源失败。请重新加载。", + "assets.searchByName": "按名称搜索", + "assets.searchByTags": "按标签搜索", + "assets.selectMany": "选择资源", + "assets.specialFolder.parent": "", + "assets.specialFolder.root": "", + "assets.tabFocusPoint": "焦点", + "assets.tabHistory": "历史", + "assets.tabImage": "图片", + "assets.tabMetadata": "元数据", + "assets.tabPreview": "预览", + "assets.tabTextEditor": "文本编辑器", + "assets.updated": "资源已更新。", + "assets.updateFailed": "更新资源失败。请重新加载。", + "assets.updateFolderFailed": "更新资源文件夹失败。请重新加载。", + "assets.uploadByDialog": "选择文件", + "assets.uploadByDrop": "将文件拖放到此处进行上传", + "assets.uploaderUploadHere": "没有正在进行的上传,将文件拖到这里。", + "assets.uploadFailed": "资源上传失败,请重新加载。", + "assets.uploadHint": "在现有项目上放置文件以使用更新版本替换资源。", + "backups.backupCountAssetsLabel": "资源", + "backups.backupCountAssetsTooltip": "存档资源", + "backups.backupCountEventsLabel": "事件", + "backups.backupCountEventsTooltip": "存档事件", + "backups.backupDownload": "下载", + "backups.backupDownloadLink": "准备就绪", + "backups.backupDuration": "持续时间", + "backups.deleteConfirmText": "你真的要删除备份吗?", + "backups.deleteConfirmTitle": "删除备份", + "backups.deleted": "备份即将被删除。", + "backups.deleteFailed": "删除备份失败。", + "backups.empty": "尚未创建备份。", + "backups.loadFailed": "加载备份失败。", + "backups.maximumReached": "您已达到最大备份数:10。", + "backups.refreshTooltip": "刷新备份 (CTRL + SHIFT + R)", + "backups.reloaded": "备份已重新加载。", + "backups.restore": "恢复备份", + "backups.restoreFailed": "无法开始恢复。", + "backups.restoreLastStatus": "上次还原操作", + "backups.restoreLastUrl": "要备份的网址", + "backups.restoreNewAppName": "可选的应用程序名称", + "backups.restorePageTitle": "恢复备份", + "backups.restoreStarted": "恢复开始,可能需要几分钟才能完成。", + "backups.restoreStartedLabel": "开始", + "backups.restoreStoppedLabel": "已停止", + "backups.restoreTitle": "恢复备份", + "backups.start": "开始备份", + "backups.started": "备份已开始,可能需要几分钟才能完成。", + "backups.startedLabel": "开始", + "backups.startFailed": "启动备份失败。", + "clients.add": "添加客户端", + "clients.addFailed": "添加客户端失败,请重新加载。", + "clients.allowAnonymous": "允许匿名访问。", + "clients.allowAnonymousHint": "允许在没有访问令牌的情况下访问通过此客户端角色配置的所有资源的 API。不要给多个客户端匿名访问。", + "clients.apiCallsLimit": "最大 API 调用数", + "clients.apiCallsLimitHint": "限制此客户端每月可以进行的 API 调用次数,以保护您的 API 队伍为其他更重要的客户端提供服务。", + "clients.clientIdValidationMessage": "名称只能包含字母、数字、破折号和空格。", + "clients.clientNamePlaceholder": "输入客户端名称", + "clients.connect": "连接", + "clients.connectWizard.cli": "连接 Squidex CLI", + "clients.connectWizard.cliHint": "下载 CLI 并连接到此应用程序以启动备份、同步Schemas或导出内容。", + "clients.connectWizard.cliStep1": "获取最新的 Squidex CLI", + "clients.connectWizard.cliStep1Download": "[从 Github 下载 CLI](https://github.com/Squidex/squidex-samples/releases)", + "clients.connectWizard.cliStep1Hint": "这些版本包含适用于所有主要操作系统的二进制文件,如果您安装了 .NET Core,则可以下载一小部分。", + "clients.connectWizard.cliStep2": "将 `<你的 Squidex CLI 下载目录>` 添加到你的 `$PATH` 变量中", + "clients.connectWizard.cliStep3": "在 CLI 配置中添加你的应用名称", + "clients.connectWizard.cliStep3Hint": "您可以在 CLI 中管理多个应用程序的配置并切换到一个应用程序。", + "clients.connectWizard.cliStep4": "在 CLI 中切换到您的应用程序", + "clients.connectWizard.manually": "手动连接", + "clients.connectWizard.manuallyHint": "获取如何与 Postman 或 curl 建立连接的说明。", + "clients.connectWizard.manuallyStep1": "使用 curl 获取令牌", + "clients.connectWizard.manuallyStep2": "只需使用以下令牌", + "clients.connectWizard.manuallyStep3": "将令牌作为 HTTP 标头添加到所有请求中", + "clients.connectWizard.manuallyTokenHint": "令牌通常会在 30 天后过期,但您可以请求多个令牌。", + "clients.connectWizard.postManDocs": "从 [文档](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman) 中的 Postman 教程开始。", + "clients.connectWizard.sdk": "使用 SDK 连接到您的应用程序", + "clients.connectWizard.sdkHelp": "你需要另一个 SDK?", + "clients.connectWizard.sdkHelpLink": "在支持论坛联系我们", + "clients.connectWizard.sdkHint": "下载 SDK 并建立与此应用程序的连接。", + "clients.connectWizard.sdkStep1": "安装.NET SDK", + "clients.connectWizard.sdkStep1Download": "SDK 可在 [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)", + "clients.connectWizard.sdkStep2": "创建客户端管理器", + "clients.connectWizard.step0Title": "设置客户端", + "clients.connectWizard.step1Title": "选择连接方式", + "clients.connectWizard.step2Title": "连接", + "clients.deleteConfirmText": "你真的要撤销客户端吗?", + "clients.deleteConfirmTitle": "撤销客户端", + "clients.empty": "尚未创建客户端。", + "clients.loadFailed": "加载客户端失败。请重新加载。", + "clients.refreshTooltip": "刷新客户端 (CTRL + SHIFT + R)", + "clients.reloaded": "客户端重新加载。", + "clients.revokeFailed": "撤销客户端失败。请重新加载。", + "clients.tokenFailed": "创建令牌失败。请重试。", + "comments.create": "创建评论", + "comments.createFailed": "创建评论失败。", + "comments.deleteConfirmText": "你真的要删除评论吗?", + "comments.deleteConfirmTitle": "删除评论", + "comments.deleteFailed": "删除评论失败。", + "comments.follow": "关注", + "comments.loadFailed": "加载评论失败。", + "comments.title": "评论", + "comments.updateFailed": "更新评论失败。", + "common.actions": "动作", + "common.administration": "管理", + "common.administrationPageTitle": "管理", + "common.api": "API", + "common.apps": "应用程序", + "common.aspectRatio": "纵横比", + "common.assets": "资源", + "common.back": "返回", + "common.backendError": "后端错误", + "common.backups": "备份", + "common.bookmarks": "书签", + "common.bytes": "bytes", + "common.cancel": "取消", + "common.category": "类别", + "common.clear": "清除", + "common.clientId": "客户端 ID", + "common.clients": "客户端", + "common.clientSecret": "客户端密码", + "common.clipboardAdded": "值已添加到您的剪贴板。", + "common.clone": "克隆", + "common.cluster": "集群", + "common.clusterPageTitle": "集群", + "common.comments": "评论", + "common.components": "组件", + "common.confirm": "确认", + "common.consumers": "消费者", + "common.content": "内容", + "common.contents": "内容", + "common.continue": "继续", + "common.contributors": "贡献者", + "common.create": "创建", + "common.created": "创建", + "common.date": "日期", + "common.dateTimeEditor.local": "本地", + "common.dateTimeEditor.now": "现在", + "common.dateTimeEditor.nowTooltip": "现在使用 (UTC)", + "common.dateTimeEditor.today": "今天", + "common.dateTimeEditor.todayTooltip": "使用今天 (UTC)", + "common.dateTimeEditor.utc": "UTC", + "common.delete": "删除", + "common.description": "说明", + "common.designer": "设计师", + "common.disabled": "已禁用", + "common.displayName": "显示名称", + "common.edit": "编辑", + "common.email": "电子邮件", + "common.enabled": "已启用", + "common.error": "错误", + "common.errorBack": "返回上一页。", + "common.errorNoPermission": "您无权执行此操作。", + "common.errorNotFound": "未找到", + "common.event": "事件", + "common.events": "事件", + "common.executed": "已执行", + "common.expertMode": "专家模式", + "common.extension": "扩展名", + "common.failed": "失败", + "common.fallback": "后备", + "common.field": "字段", + "common.files": "文件", + "common.filters": "过滤器", + "common.folder": "文件夹", + "common.folders": "文件夹", + "common.generalSettings": "通用", + "common.generate": "生成", + "common.github": "Github", + "common.height": "高度", + "common.help": "帮助", + "common.helpTour": "单击帮助图标以显示上下文特定的帮助页面。转到", + "common.hide": "隐藏", + "common.hints": "提示", + "common.history": "历史", + "common.httpConflict": "更新失败。其他用户进行了更改。请重新加载。", + "common.httpLimit": "您已超出 API 调用的最大限制。", + "common.id": "身份", + "common.label": "标签", + "common.language": "语言", + "common.languages": "语言", + "common.latitudeShort": "纬度", + "common.loading": "正在加载", + "common.logout": "注销", + "common.logs": "日志", + "common.longitudeShort": "Lon", + "common.mapHide": "隐藏地图", + "common.mapShow": "显示地图", + "common.message": "消息", + "common.name": "名称", + "common.no": "不", + "common.nothingChanged": "什么都没有改变。", + "common.noValue": "- 无值 -", + "common.or": "或", + "common.pagerInfo": "{itemFirst}-{itemLast} 的 {numberOfItems}", + "common.password": "密码", + "common.passwordConfirm": "确认密码", + "common.pattern": "模式", + "common.patterns": "模式", + "common.permissions": "权限", + "common.preview": "预览", + "common.product": "Squidex Headless CMS", + "common.project": "项目", + "common.queryOperators.contains": "包含", + "common.queryOperators.empty": "为空", + "common.queryOperators.endsWith": "以", + "common.queryOperators.eq": "等于", + "common.queryOperators.exists": "exists", + "common.queryOperators.ge": "大于或等于", + "common.queryOperators.gt": "大于", + "common.queryOperators.le": "小于或等于", + "common.queryOperators.lt": "小于", + "common.queryOperators.matchs": "匹配", + "common.queryOperators.ne": "不等于", + "common.queryOperators.startsWith": "开始于", + "common.refresh": "刷新", + "common.remember": "不要再问了", + "common.rename": "重命名", + "common.requiredHint": "必需的", + "common.reset": "重置", + "common.restore": "恢复", + "common.role": "角色", + "common.roles": "角色", + "common.rule": "规则", + "common.rules": "规则", + "common.sampleCodeLabel": "示例代码在", + "common.save": "保存", + "common.saveShortcut": "CTRL + S", + "common.schema": "Schemas", + "common.search": "搜索", + "common.searchGoogleMaps": "搜索谷歌地图", + "common.searchResults": "搜索结果", + "common.separateByLine": "按行分隔", + "common.settings": "设置", + "common.sidebar": "侧边栏扩展", + "common.sidebarTour": "侧边栏导航包含有用的上下文特定链接。您可以在此处查看此Schemas随时间变化的历史记录。", + "common.skiped": "跳过", + "common.slug": "弹头", + "common.stars.max": "不得超过 15 颗星", + "common.status": "状态", + "common.statusChangeTo": "更改为", + "common.submit": "提交", + "common.subscription": "订阅", + "common.succeeded": "成功", + "common.tagAdd": ", 添加标签", + "common.tagAddReference": ", 添加引用", + "common.tagAddSchema": ", 添加Schemas", + "common.tags": "标签", + "common.tagsAll": "所有标签", + "common.time": "时间", + "common.update": "更新", + "common.upload": "上传", + "common.url": "URL", + "common.users": "用户", + "common.value": "值", + "common.width": "宽度", + "common.workflow": "工作流程", + "common.workflows": "工作流程", + "common.yes": "是", + "contents.addComponent": "添加组件", + "contents.arrayAddItem": "添加项目", + "contents.arrayClear": "清除", + "contents.arrayClearConfirmText": "你真的要清空数组吗?", + "contents.arrayClearConfirmTitle": "清除数组", + "contents.arrayCloneItem": "克隆这个项目", + "contents.arrayCollapseAll": "折叠所有项目", + "contents.arrayCollapseItem": "收起此项", + "contents.arrayExpandAll": "展开所有项目", + "contents.arrayExpandItem": "展开此项", + "contents.arrayMoveBottom": "将此项目移到底部", + "contents.arrayMoveDown": "将此项向下移动", + "contents.arrayMoveTop": "将此项移至顶部", + "contents.arrayMoveUp": "将此项向上移动", + "contents.arrayNoFields": "先添加一个嵌套字段来添加项目。", + "contents.assetsUpload": "删除文件或点击", + "contents.autotranslate": "从母语自动翻译", + "contents.bulkFailed": "删除或更新内容失败。请重新加载。", + "contents.changeStatusTo": "将内容项更改为 {action}", + "contents.changeStatusToImmediately": "立即设置为 {action}。", + "contents.changeStatusToLater": "在稍后的日期和时间设置为 {action}。", + "contents.componentNoSchema": "添加至少一个Schemas来设置组件。", + "contents.componentsNoSchema": "添加至少一个Schemas来添加组件。", + "contents.contentNotValid": "内容元素无效,请用所有语言(如果可本地化)检查左侧带有红色条的字段。", + "contents.contentTab.editor": "编辑器", + "contents.contentTab.references": "参考资料", + "contents.contentTab.referencing": "引用", + "contents.create": "新建", + "contents.createContentTooltip": "新建内容 (CTRL + SHIFT + G)", + "contents.created": "内容创建成功。", + "contents.createdByFieldDescription": "创建内容项的用户。", + "contents.createFailed": "创建内容失败,请重新加载。", + "contents.createFieldDescription": "创建内容项的日期时间。", + "contents.createPageTitle": "创建内容", + "contents.createTitle": "新内容", + "contents.currentStatusLabel": "当前版本", + "contents.deleteConfirmText": "你真的要删除内容吗?", + "contents.deleteConfirmTitle": "删除内容", + "contents.deleteManyConfirmText": "您真的要删除选定的内容项吗?", + "contents.deleteReferrerConfirmText": "该内容被另一个内容项引用。\n\n您真的要删除该内容吗?", + "contents.deleteReferrerConfirmTitle": "删除内容", + "contents.deleteVersionConfirmText": "你真的要删除这个版本吗?", + "contents.deleteVersionFailed": "删除版本失败。请重新加载。", + "contents.draftNew": "新草稿", + "contents.draftStatus": "新版本", + "contents.editPageTitle": "编辑内容", + "contents.invariantFieldDescription": "内容项的 '{fieldName}' 字段。", + "contents.languageModeAll": "所有语言", + "contents.languageModeSingle": "单一语言", + "contents.lastModifiedByFieldDescription": "上次修改内容项的用户。", + "contents.lastModifiedFieldDescription": "上次修改内容项的日期时间。", + "contents.lastUpdatedLabel": "上次更新", + "contents.loadContent": "加载", + "contents.loadContentFailed": "加载内容失败,请重新加载。", + "contents.loadDataFailed": "加载数据失败,请重新加载。", + "contents.loadFailed": "加载内容失败,请重新加载。", + "contents.loadVersionFailed": "加载新版本失败。请重新加载。", + "contents.localizedFieldDescription": "内容项的 '{fieldName}' 字段(本地化)。", + "contents.newStatusFieldDescription": "内容项的新状态。", + "contents.noReference": "- 无引用 -", + "contents.noReferences": "此内容没有引用。", + "contents.noReferencing": "此内容未被其他项目引用。", + "contents.pendingChangesTextToChange": "您有未保存的更改。\n\n当您更改状态时,您将丢失它们。\n\n**仍要继续吗?**", + "contents.pendingChangesTextToClose": "您有未保存的更改。\n\n当您关闭当前内容视图时,您将丢失它们。\n\n**您是否仍要继续?**", + "contents.pendingChangesTextToPreview": "您有未保存的更改。\n\n您不会在预览中看到它们。\n\n**您是否仍要继续?**", + "contents.pendingChangesTitle": "未保存的更改", + "contents.publishAll": "全部发布", + "contents.referencesCreateNew": "新增", + "contents.referencesCreatePublish": "创建和发布", + "contents.referencesLink": "链接所选内容 ({count})", + "contents.referencesSelectExisting": "选择现有", + "contents.referencesSelectSchema": "选择 {schema}", + "contents.refreshTooltip": "刷新内容 (CTRL + SHIFT + R)", + "contents.reloaded": "内容已重新加载。", + "contents.removeConfirmText": "您真的要删除内容吗?", + "contents.removeConfirmTitle": "删除内容", + "contents.saveAndPublish": "保存并发布", + "contents.scheduledAt": "at", + "contents.scheduledAtLabel": "at", + "contents.scheduledTo": "to", + "contents.schemasPageTitle": "内容", + "contents.searchPlaceholder": "全文搜索", + "contents.searchSchemasPlaceholder": "搜索Schemas...", + "contents.selectionCount": "{count} 个选定的项目", + "contents.statusFieldDescription": "内容项的状态。", + "contents.statusQueries": "状态查询", + "contents.stockPhotoEmpty": "未选择任何内容", + "contents.stockPhotoSearch": "通过 Unsplash 搜索照片", + "contents.tableHeaders.created": "创建", + "contents.tableHeaders.createdBy": "创建者", + "contents.tableHeaders.createdByShort": "By", + "contents.tableHeaders.id": "Id", + "contents.tableHeaders.lastModified": "更新", + "contents.tableHeaders.lastModifiedBy": "更新者", + "contents.tableHeaders.lastModifiedByShort": "By", + "contents.tableHeaders.nextStatus": "下一个状态", + "contents.tableHeaders.status": "状态", + "contents.tableHeaders.version": "版本", + "contents.unpublishReferrerConfirmText": "该内容被另一个已发布的内容项引用。\n\n您真的要取消发布此内容吗?", + "contents.unpublishReferrerConfirmTitle": "取消发布内容", + "contents.unsavedChangesText": "您有未保存的更改。要立即加载吗?", + "contents.unsavedChangesTitle": "未保存的更改", + "contents.unsetValue": "未设置值", + "contents.unsetValueConfirmText": "如果您取消设置该值,您可能会丢失更改。\n\n您真的要这样做吗?", + "contents.unsetValueConfirmTitle": "你想取消设置吗?", + "contents.updated": "内容更新成功。", + "contents.updateFailed": "更新内容失败,请重新加载。", + "contents.validate": "验证", + "contents.validationHint": "当您看到验证错误时,请记住检查所有语言。", + "contents.versionCompare": "比较", + "contents.versionDelete": "删除此版本", + "contents.versionFieldDescription": "内容项的版本", + "contents.versionViewing": "查看版本**{version}**。", + "contents.viewLatest": "查看最新", + "contents.viewReset": "重置默认视图", + "contributors.add": "添加贡献者", + "contributors.addFailed": "添加贡献者失败。请重新加载。", + "contributors.contributorAssigned": "一个具有输入电子邮件地址的新用户已被创建并被指定为贡献者。", + "contributors.contributorAssignedExisting": "用户已被分配", + "contributors.contributorAssignedInvited": "用户已被邀请并分配。", + "contributors.contributorAssignedOld": "用户已被添加为贡献者。", + "contributors.deleteConfirmText": "你真的要移除贡献者吗?", + "contributors.deleteConfirmTitle": "移除贡献者", + "contributors.deleteFailed": "删除贡献者失败。请重新加载。", + "contributors.emailPlaceholder": "查找现有用户或通过电子邮件邀请", + "contributors.empty": "没有找到贡献者。", + "contributors.import.emailsDetected": "检测到的电子邮件:{count}", + "contributors.import.run": "添加贡献者", + "contributors.import.run2": "导入", + "contributors.importButton": "一次添加多个贡献者", + "contributors.importHintg": "大团队?", + "contributors.importTitle": "导入贡献者", + "contributors.loadFailed": "加载贡献者失败。请重新加载。", + "contributors.planHint": "您的计划允许最多 {maxContributors} 个贡献者。", + "contributors.refreshTooltip": "刷新贡献者 (CTRL + SHIFT + R)", + "contributors.reloaded": "贡献者重新加载。", + "contributors.search": "搜索", + "contributors.userNotFound": "用户不存在。", + "dashboard.apiCallsCard": "API 调用", + "dashboard.apiCallsChart": "API 调用图表", + "dashboard.apiCallsLimitLabel": "每月限制", + "dashboard.apiCallsSummaryCard": "API 调用摘要", + "dashboard.apiDocumentationCard": "API 文档", + "dashboard.apiPerformanceCard": "API 性能 (ms): {summary}ms avg", + "dashboard.apiPerformanceChart": "API 性能图表", + "dashboard.assetSizeCard": "资源大小 (MB", + "dashboard.assetSizeLabel": "总大小", + "dashboard.assetSizeLimitLabel": "总限制", + "dashboard.assetTotalSize": "资源总存储大小", + "dashboard.assetUpdloadsCountChart": "资源上传计数图表", + "dashboard.assetUploadsCard": "资源上传", + "dashboard.assetUploadsSizeChart": "资源上传大小图表", + "dashboard.configSaved": "配置已保存。", + "dashboard.contentApi": "内容 API", + "dashboard.contentApiDescription": "适用于您的应用内容的 OpenAPI 3.0 兼容文档。", + "dashboard.contentsSummaryCard": "项目数量", + "dashboard.currentMonthLabel": "本月", + "dashboard.downloadLog": "下载日志", + "dashboard.editConfig": "编辑配置", + "dashboard.githubCard": "Github", + "dashboard.githubCardDescription": "从 Github 获取源代码并报告错误或寻求支持。", + "dashboard.historyCard": "历史", + "dashboard.pageTitle": "仪表盘", + "dashboard.resetConfigConfirmText": "您真的要将仪表板重置为默认设置吗?", + "dashboard.resetConfigConfirmTitle": "重置配置", + "dashboard.schemaNewCard": "新Schemas", + "dashboard.schemaNewCardDescription": "Schemas定义了内容元素的结构。", + "dashboard.schemasCard": "Schemas", + "dashboard.schemasCardDescription": "深入了解此应用的数据模型。", + "dashboard.stackedChart": "折叠", + "dashboard.supportCard": "反馈与支持", + "dashboard.supportCardDescription": "提供反馈和请求功能以帮助我们改进 Squidex。", + "dashboard.trafficChart": "API 流量图表", + "dashboard.trafficHeader": "流量 (MB)", + "dashboard.trafficLimitLabel": "每月限制", + "dashboard.trafficSummaryCard": "API 流量汇总", + "dashboard.welcomeText": "欢迎使用 **{app}** 仪表板。", + "dashboard.welcomeTitle": "Hi {user}", + "eventConsumers.count": "计数", + "eventConsumers.loadFailed": "加载事件消费者失败。请重新加载。", + "eventConsumers.pageTitle": "事件消费者", + "eventConsumers.position": "位置", + "eventConsumers.refreshTooltip": "刷新事件消费者 (CTRL + SHIFT + R)", + "eventConsumers.reloaded": "事件消费者重新加载。", + "eventConsumers.resetFailed": "无法重置事件消费者。请重新加载。", + "eventConsumers.resetTooltip": "重置事件消费者", + "eventConsumers.startFailed": "无法启动事件消费者。请重新加载。", + "eventConsumers.startTooltip": "启动事件消费者", + "eventConsumers.stopFailed": "无法停止事件消费者。请重新加载。", + "eventConsumers.stopTooltip": "停止事件消费者", + "features.loadFailed": "加载功能失败。请重新加载。", + "history.loadFailed": "加载历史记录失败。请重新加载。", + "history.title": "活动", + "languages.add": "添加语言", + "languages.addFailed": "添加语言失败。请重新加载。", + "languages.deleteConfirmText": "你真的要删除语言吗?", + "languages.deleteConfirmTitle": "删除语言", + "languages.deleteFailed": "删除语言失败。请重新加载。", + "languages.loadFailed": "加载语言失败。请重新加载。", + "languages.master": "是大师", + "languages.masterHint": "如果没有定义回退,其他语言回退到母版。", + "languages.optional": "是可选的", + "languages.optionalHint": "不得输入可选语言的值,即使字段是必需的。", + "languages.refreshTooltip": "刷新语言 (CTRL + SHIFT + R)", + "languages.reloaded": "语言已重新加载。", + "languages.updateFailed": "更改语言失败。请重新加载。", + "news.headline": "有什么新鲜事?", + "news.title": "新功能", + "notifo.subscripeTooltip": "单击此按钮可订阅所有更改并接收推送通知。", + "plans.billingPortal": "计费门户", + "plans.billingPortalHint": "前往账单门户查看付款历史和订阅概览。", + "plans.change": "改变", + "plans.changeConfirmTitle": "更改订阅", + "plans.changeFailed": "更改计划失败。请重新加载。", + "plans.includedCalls": "API 调用", + "plans.includedContributors": "贡献者", + "plans.includedStorage": "存储", + "plans.includedTraffic": "交通", + "plans.loadFailed": "加载计划失败。请重新加载。", + "plans.noPlanConfigured": "未配置计划,此应用无限制使用。", + "plans.notPlanOwner": "您尚未创建订阅。因此您无法更改计划。", + "plans.perMonth": "每月", + "plans.perYear": "每年", + "plans.refreshTooltip": "刷新计划 (CTRL + SHIFT + R)", + "plans.reloaded": "计划重新加载。", + "plans.selected": "已选择", + "profile.title": "个人资料", + "profile.userEmail": "登录方式", + "roles.add": "添加角色", + "roles.addFailed": "添加角色失败,请重新加载。", + "roles.default.owner": "可以做任何事情,包括删除应用程序。", + "roles.default.reader": "只能读取资源和内容。", + "roles.defaults.developer": "可以使用 API 视图,编辑资源、内容、Schemas、规则、工作流和 appSettings.patterns。", + "roles.defaults.editor": "可以编辑资源和内容并查看工作流程。", + "roles.deleteConfirmText": "删除角色", + "roles.deleteConfirmTitle": "你真的要删除角色吗?", + "roles.loadFailed": "加载角色失败。请重新加载。", + "roles.loadPermissionsFailed": "加载权限失败。请重新加载。", + "roles.permissions": "权限", + "roles.permissionsDescription": "权限在 API 级别限制允许的操作和查询,是一项安全功能。", + "roles.permissionsPlaceholder": "开始输入以搜索权限", + "roles.properties": "属性", + "roles.properties.hideAPI": "隐藏 API", + "roles.properties.hideAssets": "隐藏资源", + "roles.properties.hideContents": "隐藏 {schema} 内容", + "roles.properties.hideSchemas": "隐藏Schemas", + "roles.properties.hideSettings": "隐藏设置", + "roles.propertiesDescription": "属性描述管理 UI 的行为,但不为 API 提供安全性。", + "roles.refreshTooltip": "刷新角色 (CTRL + SHIFT + R)", + "roles.reloaded": "重新加载角色。", + "roles.revokeFailed": "撤销角色失败,请重新加载。", + "roles.roleNamePlaceholder": "输入角色名称", + "roles.updateFailed": "更新角色失败。请重新加载。", + "rules.actionData": "动作数据", + "rules.actionHint": "动作类型的选择以后不能更改。", + "rules.cancelFailed": "取消规则失败,请重新加载。", + "rules.create": "新规则", + "rules.createFailed": "创建规则失败。请重新加载。", + "rules.createTooltip": "新规则 (CTRL + SHIFT + G)", + "rules.deleteConfirmText": "你真的要删除规则吗?", + "rules.deleteConfirmTitle": "删除规则", + "rules.deleteFailed": "删除规则失败,请重新加载。", + "rules.empty": "尚未创建规则。", + "rules.emptyAddRule": "添加规则", + "rules.enqueued": "规则已加入队列。", + "rules.itemPageTitle": "规则", + "rules.listPageTitle": "规则", + "rules.loadFailed": "加载规则失败。请重新加载。", + "rules.readMore": "阅读更多", + "rules.refreshEventsTooltip": "刷新事件 (CTRL + SHIFT + R)", + "rules.refreshTooltip": "刷新规则 (CTRL + SHIFT + R)", + "rules.reloaded": "规则重新加载。", + "rules.restarted": "规则将在几秒钟后开始运行。", + "rules.ruleEvents.cancelFailed": "取消规则事件失败。请重新加载。", + "rules.ruleEvents.enqueue": "入队", + "rules.ruleEvents.enqueued": "事件已入队。将在几秒钟后重新发送。", + "rules.ruleEvents.enqueueFailed": "无法将规则事件入队。请重新加载。", + "rules.ruleEvents.lastInvokedLabel": "上次调用", + "rules.ruleEvents.listPageTitle": "规则事件", + "rules.ruleEvents.loadFailed": "加载事件失败。请重新加载。", + "rules.ruleEvents.nextAttemptLabel": "下一个", + "rules.ruleEvents.numAttemptsLabel": "尝试次数", + "rules.ruleEvents.reloaded": "RuleEvents 重新加载。", + "rules.ruleSimulator.listPageTitle": "模拟器", + "rules.ruleSyntax.if": "如果", + "rules.ruleSyntax.then": "那么", + "rules.run": "运行", + "rules.runFailed": "运行规则失败。请重新加载。", + "rules.runFromSnapshots": "以最新状态运行", + "rules.runningRule": "规则 '{name}' 当前正在运行。", + "rules.runRuleConfirmText": "你真的想为所有事件运行规则吗?", + "rules.runRuleConfirmTitle": "运行规则", + "rules.simulate": "模拟", + "rules.simulateTooltip": "使用最近 100 个事件模拟此规则。", + "rules.simulator": "模拟器", + "rules.stop": "规则很快就会停止。", + "rules.triggerConfirmText": "你真的要触发规则吗?", + "rules.triggerConfirmTitle": "触发规则", + "rules.triggerFailed": "触发规则失败。请重新加载。", + "rules.triggerHint": "以后不能更改触发器类型的选择。", + "rules.unnamed": "未命名规则", + "rules.updateFailed": "更新规则失败。请重新加载。", + "schema.clone": "克隆Schemas", + "schema.create": "创建Schemas", + "schema.field.defaultValue": "默认值", + "schema.field.editor": "编辑器", + "schema.field.hiddenMarker": "隐藏", + "schema.field.localizable": "Localizable", + "schema.field.localizableMarker": "localizable", + "schema.field.placeholder": "占位符", + "schema.field.required": "必需", + "schema.field.tabEditing": "编辑", + "schema.field.tabValidation": "验证", + "schema.field.unique": "唯一", + "schema.field.visibleMarker": "可见", + "schema.fieldTypes.array.count": "项目", + "schema.fieldTypes.assets.size": "大小", + "schema.fieldTypes.string.length": "长度", + "schema.fieldTypes.string.lengthMax": "最大长度", + "schema.fieldTypes.string.pattern": "正则表达式", + "schema.loadSchemaFailed": "加载Schemas失败。请重新加载。", + "schema.modeComponent": "组件", + "schema.published": "已发布", + "schema.reloaded": "Schemas已重新加载。", + "schema.saved": "Schemas保存成功。", + "schema.synchronized": "Schemas同步成功。", + "schema.tabMore": "更多", + "schema.tabScripts": "脚本", + "schema.tabUI": "UI", + "schema.unpublished": "未发布", + "schemas.addField": "添加字段", + "schemas.addFieldAndClose": "创建并关闭", + "schemas.addFieldAndCreate": "创建并添加字段", + "schemas.addFieldAndEdit": "创建和编辑字段", + "schemas.addFieldButton": "添加字段", + "schemas.addFieldFailed": "添加字段失败。请重新加载。", + "schemas.addNestedField": "添加嵌套字段", + "schemas.changeCategoryFailed": "更改类别失败。请重新加载。", + "schemas.contentEditorUrl": "内容编辑器扩展", + "schemas.contentSidebarUrl": "内容侧边栏扩展", + "schemas.contentSidebarUrlHint": "详细信息视图中侧边栏插件的 URL。", + "schemas.contentsSidebarUrl": "内容侧边栏扩展", + "schemas.contentsSidebarUrlHint": "列表视图中侧边栏插件的 URL。", + "schemas.contextMenuTour": "打开上下文菜单以删除Schemas或为内容更改创建一些脚本。", + "schemas.createCategory": "创建新类别...", + "schemas.createFailed": "无法创建Schemas。请重新加载。", + "schemas.createSchemaTooltip": "新Schemas (CTRL + SHIFT + G)", + "schemas.deleteConfirmText": "您真的要删除Schemas吗?", + "schemas.deleteConfirmTitle": "删除Schemas", + "schemas.deleteFailed": "删除Schemas失败。请重新加载。", + "schemas.deleteFieldFailed": "删除字段失败。请重新加载。", + "schemas.deleteRuleConfirmText": "你真的要删除这个字段规则吗?", + "schemas.deleteRuleConfirmTitle": "删除字段规则", + "schemas.deleteUrlConfirmText": "你真的要删除这个 URL 吗?", + "schemas.deleteUrlConfirmTitle": "删除 URL", + "schemas.disableFieldFailed": "禁用字段失败。请重新加载。", + "schemas.enableFieldFailed": "无法启用字段。请重新加载。", + "schemas.export.deleteFields": "删除字段", + "schemas.export.recreateFields": "重新创建字段", + "schemas.export.synchronize": "同步", + "schemas.field.allowedValues": "允许的值", + "schemas.field.defaultValues": "默认值", + "schemas.field.defaultValuesHint": "设置每种语言的默认值并覆盖默认值属性(如果已定义)。仅在真正需要时才使用它。", + "schemas.field.deleteConfirmText": "您真的要删除该字段吗?", + "schemas.field.deleteConfirmTitle": "删除字段", + "schemas.field.disable": "在 UI 中禁用", + "schemas.field.disabledMarker": "已禁用", + "schemas.field.editorUrl": "编辑器网址", + "schemas.field.editorUrlHint": "如果您使用自定义编辑器,请访问您的插件的 URL。", + "schemas.field.empty": "尚未创建字段。", + "schemas.field.enable": "在 UI 中启用", + "schemas.field.enabledMarker": "已启用", + "schemas.field.halfWidth": "半宽", + "schemas.field.halfWidthHint": "在编辑或创建页面上,当有足够的空间时,只显示半宽的字段。", + "schemas.field.hide": "隐藏在 API", + "schemas.field.hintsHint": "为文档和 UI 描述这个字段。", + "schemas.field.inlineEditable": "内联可编辑", + "schemas.field.labelHint": "文档和 UI 的显示名称。", + "schemas.field.localizableHint": "您可以将字段标记为可本地化。这意味着这取决于语言,例如城市名称。", + "schemas.field.lock": "锁定并防止更改", + "schemas.field.lockConfirmText": "警告:无法撤消锁定字段!锁定的字段定义无法再解锁、删除或更改。\n\n您真的要锁定此字段吗?", + "schemas.field.lockConfirmTitle": "锁定字段", + "schemas.field.lockedMarker": "锁定", + "schemas.field.nameHint": "API 响应中的字段名称。", + "schemas.field.namePlaceholder": "输入字段名称", + "schemas.field.nameValidationMessage": "名称必须是骆驼大小写的有效 javascript 名称。", + "schemas.field.placeholderHint": "定义输入控件的占位符。", + "schemas.field.requiredOnPublish": "发布时需要", + "schemas.field.show": "在 API 中显示", + "schemas.field.tabCommon": "通用", + "schemas.field.tagsHint": "为自动化流程注释您的领域的标签。", + "schemas.fieldTypes.array.countMax": "最大项目数", + "schemas.fieldTypes.array.countMin": "最小项目", + "schemas.fieldTypes.array.description": "嵌入对象列表。", + "schemas.fieldTypes.assets.allowDuplicates": "允许重复值", + "schemas.fieldTypes.assets.count": "计数", + "schemas.fieldTypes.assets.countMax": "最大资源", + "schemas.fieldTypes.assets.countMin": "最小资源", + "schemas.fieldTypes.assets.description": "图片、视频、文档。", + "schemas.fieldTypes.assets.fileExtensions": "文件扩展名", + "schemas.fieldTypes.assets.folderId": "文件夹", + "schemas.fieldTypes.assets.folderIdHint": "新资源将上传到的资源文件夹。", + "schemas.fieldTypes.assets.mustBeImage": "必须是图片", + "schemas.fieldTypes.assets.previewFileName": "仅文件名", + "schemas.fieldTypes.assets.previewImage": "如果不是图像,则只有缩略图或文件名", + "schemas.fieldTypes.assets.previewImageAndFileName": "缩略图和文件名", + "schemas.fieldTypes.assets.previewMode": "PreviewMode", + "schemas.fieldTypes.assets.previewModeHint": "内容列表中资源的预览模式。", + "schemas.fieldTypes.assets.resolve": "解析第一个资源", + "schemas.fieldTypes.assets.resolveHint": "显示内容列表中第一个引用的资源。", + "schemas.fieldTypes.assets.sizeMax": "最小尺寸", + "schemas.fieldTypes.assets.sizeMin": "最大尺寸", + "schemas.fieldTypes.boolean.description": "是或否,对或错。", + "schemas.fieldTypes.component.description": "在此内容中嵌入另一个Schemas。", + "schemas.fieldTypes.components.description": "将其他Schemas作为数组嵌入到此内容中。", + "schemas.fieldTypes.dateTime.defaultMode": "默认模式", + "schemas.fieldTypes.dateTime.description": "活动日期,开放时间。", + "schemas.fieldTypes.dateTime.rangeMax": "最大值", + "schemas.fieldTypes.dateTime.rangeMin": "最小值", + "schemas.fieldTypes.geolocation.description": "坐标:纬度和经度。", + "schemas.fieldTypes.json.description": "JSON 格式的数据,供开发人员使用。", + "schemas.fieldTypes.number.description": "ID、订单号、评级、数量。", + "schemas.fieldTypes.number.range": "范围", + "schemas.fieldTypes.number.rangeMax": "最大值", + "schemas.fieldTypes.number.rangeMin": "最小值", + "schemas.fieldTypes.references.count": "项目", + "schemas.fieldTypes.references.countMax": "最大项目数", + "schemas.fieldTypes.references.countMin": "最小项目", + "schemas.fieldTypes.references.description": "链接到其他内容项。", + "schemas.fieldTypes.references.mustBePublished": "必须发布参考文献", + "schemas.fieldTypes.references.resolveHint": "当 MaxItems 设置为 1 时,在内容列表中显示引用项的名称。", + "schemas.fieldTypes.string.characters": "字符", + "schemas.fieldTypes.string.charactersMax": "最大字符数", + "schemas.fieldTypes.string.charactersMin": "最小字符数", + "schemas.fieldTypes.string.contentType": "内容类型", + "schemas.fieldTypes.string.description": "标题、名称、段落。", + "schemas.fieldTypes.string.folderId": "资源文件夹", + "schemas.fieldTypes.string.folderIdHint": "新资源将上传到的资源文件夹。", + "schemas.fieldTypes.string.lengthMin": "最小长度", + "schemas.fieldTypes.string.patternMessage": "模式消息", + "schemas.fieldTypes.string.suggestions": "建议", + "schemas.fieldTypes.string.wordHint": "字数和字符数在纯文本上计算。纯文本根据定义的内容类型计算,可以是 Markdown 或 HTML。", + "schemas.fieldTypes.string.words": "单词", + "schemas.fieldTypes.string.wordsMax": "最大字数", + "schemas.fieldTypes.string.wordsMin": "最小字数", + "schemas.fieldTypes.tags.count": "项目", + "schemas.fieldTypes.tags.countMax": "最大项目数", + "schemas.fieldTypes.tags.countMin": "最小项目", + "schemas.fieldTypes.tags.description": "标签的特殊格式。", + "schemas.fieldTypes.ui.description": "编辑 UI 的分隔符。", + "schemas.hideFieldFailed": "隐藏字段失败。请重新加载。", + "schemas.import": "导入Schemas", + "schemas.listFields": "列表字段", + "schemas.listFieldsEmpty": "将字段拖放到此处或重新排序以显示内容列表中的字段。当未定义列表字段时,使用第一个字段。", + "schemas.loadFailed": "加载Schemas失败。请重新加载。", + "schemas.lockFieldFailed": "无法锁定字段。请重新加载。", + "schemas.modeComponentDescription": "只能嵌入到组件字段...", + "schemas.modeMultiple": "多个内容", + "schemas.modeMultipleDescription": "最适合多个实例,如博客文章、页面、作者、产品...", + "schemas.modeSingle": "单一内容", + "schemas.modeSingleDescription": "最适合单个实例,如主页、隐私政策、设置...", + "schemas.nameWarning": "这些值以后不能更改。", + "schemas.previewUrls.empty": "未配置预览网址。", + "schemas.previewUrls.help": "查看集成帮助页面以了解有关预览 URL 的更多信息。", + "schemas.previewUrls.namePlaceholder": "网络或移动", + "schemas.previewUrls.title": "预览网址", + "schemas.previewUrls.urlPlaceholder": "带变量的 URL", + "schemas.publishedTour": "请注意,您必须先发布Schemas,然后才能向其中添加内容。", + "schemas.publishFailed": "无法发布Schemas。请重新加载。", + "schemas.referenceFields": "参考字段", + "schemas.referenceFieldsEmpty": "将字段拖放到此处或重新排序以在被其他内容引用时显示字段。当未定义引用字段时,将使用列表字段代替。", + "schemas.reorderFieldsFailed": "重新排序字段失败。请重新加载。", + "schemas.rules.action": "动作", + "schemas.rules.condition": "Javascript 中的条件", + "schemas.rules.empty": "没有配置字段规则。", + "schemas.rules.title": "字段规则", + "schemas.rules.when": "何时", + "schemas.saveFieldAndClose": "保存并关闭", + "schemas.saveFieldAndNew": "保存并添加字段", + "schemas.schemaHintsHint": "为文档和用户界面描述这个Schemas。", + "schemas.schemaLabelHint": "文档和用户界面的显示名称。", + "schemas.schemaNameHint": "您只能使用字母、数字和破折号,并且不能超过 40 个字符。", + "schemas.schemaNameValidationMessage": "名称只能包含字母、数字、破折号和空格。", + "schemas.schemaTagsHint": "用于注释自动化流程Schemas的标签。", + "schemas.searchPlaceholder": "搜索Schemas...", + "schemas.showFieldFailed": "显示字段失败。请重新加载。", + "schemas.synchronizeFailed": "同步Schemas失败。请重新加载。", + "schemas.tabFields": "字段", + "schemas.tabJson": "Json", + "schemas.ui": "指定的字段", + "schemas.ui.unassignedFields": "未分配的字段", + "schemas.unpublishFailed": "无法取消发布Schemas。请重新加载。", + "schemas.updateFailed": "更新Schemas失败。请重新加载。", + "schemas.updateFieldFailed": "更新字段失败。请重新加载。", + "schemas.updatePreviewUrlsFailed": "无法配置预览网址。请重新加载。", + "schemas.updateRulesFailed": "更新Schemas规则失败。请重新加载。", + "schemas.updateScriptsFailed": "更新Schemas脚本失败。请重新加载。", + "schemas.updateUIFieldsFailed": "无法更新 UI 字段。请重新加载。", + "schemas.validateOnPublish": "发布时验证", + "search.addFilter": "添加过滤器", + "search.addGroup": "添加组", + "search.addSorting": "添加排序", + "search.advancedTour": "单击此图标可显示高级搜索菜单!", + "search.customQuery": "自定义查询", + "search.fullTextTour": "使用全文搜索在所有领域和语言中搜索内容!", + "search.help": "在 [文档](https://docs.squidex.io/04-guides/02-api.html) 中阅读有关过滤的更多信息。", + "search.myQueries": "我的查询", + "search.nameQuery": "命名查询", + "search.queriesEmpty": "搜索 {types} 并在搜索表单中使用 图标来保存所有贡献者的查询。", + "search.queryAllNewestFirst": "全部(最新的在前)", + "search.queryAllOldestFirst": "所有(最旧的在前)", + "search.quickNavPlaceholder": "快速导航(按'q')", + "search.saveQueryMyself": "只为我保存查询。", + "search.searchFailed": "搜索失败,请重新加载。", + "search.sharedQueries": "共享查询", + "search.sorting": "排序", + "start.login": "登录 Squidex", + "start.loginHint": "登录按钮将打开一个新的弹出窗口。一旦您登录成功,我们会将您重定向到 Squidex 管理门户。", + "start.madeBy": "自豪地制作", + "start.madeByCopyright": "Sebastian Stehle 和贡献者,2016-2021", + "tour.joinForum": "加入我们的论坛", + "tour.joinGithub": "加入我们的 Github", + "tour.skip": "跳过游览", + "tour.step0Next": "我们看看周围", + "tour.step0Text": "您可以立即开始管理和分发您的内容,但我们想先向您介绍一些基础知识...\n\n如何", + "tour.step1Next": "继续", + "tour.step1Text": "应用程序是您项目的存储库,例如(博客、网上商店或移动应用程序)。您可以为您的应用程序分配贡献者以协同工作。\n\n您可以在其中创建无限数量的应用程序Squidex 同时管理多个项目。", + "tour.step2Next": "继续!", + "tour.step2Text": "Schemas定义内容的结构、内容项的字段和数据类型。\n\n在向Schemas添加内容之前,请确保点击顶部的“发布”按钮使Schemas可用于您的内容 appSettings.editors。", + "tour.step3Next": "快到了!", + "tour.step3Text": "内容是您的应用程序中按Schemas分组的实际数据。\n\n首先选择一个已发布的Schemas,然后为此Schemas添加内容。", + "tour.step4Next": "知道了!", + "tour.step4Text": "资源包含所有也可以链接到您的内容的文件。例如图像、视频或文档。\n\n您可以在此处上传资源供以后使用,也可以在创建时直接上传带有资源字段的新内容项。", + "tour.step5Text": "但这还不是我们可以提供的全部支持。\n\n您可以访问 https://docs.squidex.io/> 阅读更多信息。\n\n您想加入我们的社区吗? ?", + "tour.step5Title": "太棒了,现在你知道基础了!", + "tour.tooltipConfirm": "知道了", + "tour.tooltipStop": "停止游览", + "tour.welcome": "欢迎来到", + "tour.welcomeProduct": "Squidex CMS", + "translate.translateFailed": "无法翻译文本。请重新加载。", + "usages.loadCallsFailed": "加载调用使用失败。请重新加载。", + "usages.loadMonthlyCallsFailed": "未能加载每月 API 调用。请重新加载。", + "usages.loadStorageFailed": "加载存储使用失败。请重新加载。", + "usages.loadTodayStorageFailed": "无法加载今天的存储大小。请重新加载。", + "users.create": "新建", + "users.createFailed": "创建用户失败,请重新加载。", + "users.createPageTitle": "创建用户", + "users.createTitle": "新用户", + "users.createTooltip": "新用户 (CTRL + N)", + "users.deleteConfirmText": "你真的要删除这个用户吗?", + "users.deleteConfirmTitle": "删除用户", + "users.deleteFailed": "删除用户失败,请重新加载。", + "users.editPageTitle": "编辑用户", + "users.editTitle": "编辑用户", + "users.listPageTitle": "用户管理", + "users.listTitle": "用户", + "users.loadFailed": "加载用户失败,请重新加载。", + "users.loadUserFailed": "加载用户失败。请重新加载。", + "users.lockFailed": "锁定用户失败。请重新加载。", + "users.lockTooltip": "锁定用户", + "users.passwordConfirmValidationMessage": "密码必须相同。", + "users.refreshTooltip": "刷新用户 (CTRL + SHIFT + R)", + "users.reloaded": "用户重新加载。", + "users.search": "搜索用户", + "users.unlockFailed": "解锁用户失败。请重新加载。", + "users.unlockTooltip": "解锁用户", + "users.updateFailed": "更新用户失败。请重新加载。", + "validation.between": "{field} 必须介于 '{min}' 和 '{max}' 之间。", + "validation.betweenlength": "{field|upper} 必须介于 {minlength} 和 {maxlength} 项之间。", + "validation.betweenlengthstring": "{field|upper} 必须介于 {minlength} 和 {maxlength} 个字符之间。", + "validation.email": "{field|upper} 必须是电子邮件地址。", + "validation.exactly": "{field|upper} 必须正好是 '{expected}'。", + "validation.exactlylength": "{field|upper} 必须正好有 {expected} 项。", + "validation.exactlylengthstring": "{field|upper} 必须正好有 {expected} 个字符。", + "validation.match": "{message}", + "validation.max": "{field|upper} 必须小于或等于 '{max}'。", + "validation.maxlength": "{field|upper} 不能超过 {requiredlength} 个项目。", + "validation.maxlengthstring": "{field|upper} 不能超过 {requiredlength} 个字符。", + "validation.min": "{field|upper} 必须大于或等于 '{min}'。", + "validation.minlength": "{field|upper} 必须至少有 {requiredlength} 项。", + "validation.minlengthstring": "{field|upper} 必须至少有 {requiredlength} 个字符。", + "validation.pattern": "{field|upper} 与模式不匹配。", + "validation.patternmessage": "{message}", + "validation.required": "{field|upper} 是必需的。", + "validation.requiredTrue": "{field|upper} 是必需的。", + "validation.uniquestrings": "{field|upper} 不得包含重复值。", + "validation.validarrayvalues": "{field|upper} 包含无效值:{invalidvalue}。", + "validation.validdatetime": "{field|upper} 不是有效的日期时间。", + "validation.validvalues": "{field|upper} 不是一个有效值。", + "workflows.add": "添加工作流", + "workflows.addStep": "添加步骤", + "workflows.createFailed": "创建工作流失败。请重新加载。", + "workflows.deleteConfirmText": "你真的要删除工作流吗?", + "workflows.deleteConfirmTitle": "删除工作流", + "workflows.deleteFailed": "删除工作流失败。请重新加载。", + "workflows.empty": "尚未创建工作流。", + "workflows.loadFailed": "加载工作流失败。请重新加载。", + "workflows.notNamed": "未命名的工作流程", + "workflows.preventUpdates": "防止更新", + "workflows.publishedNotRemovable": "无法删除", + "workflows.refreshTooltip": "刷新工作流 (CTRL + SHIFT + R)", + "workflows.reloaded": "工作流已重新加载。", + "workflows.saved": "工作流已保存。", + "workflows.schemasHint": "将此工作流限制为特定Schemas或将其保留为所有Schemas的空。", + "workflows.syntax.expression": "表达式", + "workflows.syntax.for": "for", + "workflows.syntax.when": "when", + "workflows.tabEdit": "编辑", + "workflows.tabVisualize": "可视化", + "workflows.updateFailed": "无法更新工作流。请重新加载。", + "workflows.workflowNameHint": "工作流的可选名称。", + "workflows.workflowNamePlaceholder": "输入工作流名称" +} \ No newline at end of file diff --git a/backend/i18n/translator/Squidex.Translator/Commands.cs b/backend/i18n/translator/Squidex.Translator/Commands.cs index 64ea32a9d..4a6425a75 100644 --- a/backend/i18n/translator/Squidex.Translator/Commands.cs +++ b/backend/i18n/translator/Squidex.Translator/Commands.cs @@ -89,6 +89,26 @@ namespace Squidex.Translator new GenerateFrontendResources(folder, service).Run(); } + [Command(Name = "clean-backend", Description = "Clean the backend translations.")] + public void CleanBackend(TranslateArguments arguments) + { + var (folder, service) = Setup(arguments, "backend"); + + Helper.CleanOtherLocales(service); + + service.Save(); + } + + [Command(Name = "clean-frontend", Description = "Clean the frontend translations.")] + public void CleanFrontend(TranslateArguments arguments) + { + var (folder, service) = Setup(arguments, "frontend"); + + Helper.CleanOtherLocales(service); + + service.Save(); + } + [Command(Name = "gen-keys", Description = "Generate the keys for translations.")] public void GenerateBackendKeys(TranslateArguments arguments) { @@ -124,18 +144,18 @@ namespace Squidex.Translator throw new ArgumentException("Folder does not exist."); } - var supportedLocaled = new string[] { "en", "nl", "it" }; + var supportedLocales = new string[] { "en", "nl", "it", "zh" }; - var locales = supportedLocaled; + var locales = supportedLocales; if (arguments.Locales != null && arguments.Locales.Any()) { - locales = supportedLocaled.Intersect(arguments.Locales).ToArray(); + locales = supportedLocales.Intersect(arguments.Locales).ToArray(); } if (locales.Length == 0) { - locales = supportedLocaled; + locales = supportedLocales; } var translationsDirectory = new DirectoryInfo(Path.Combine(arguments.Folder, "backend", "i18n")); diff --git a/backend/i18n/translator/Squidex.Translator/Processes/Helper.cs b/backend/i18n/translator/Squidex.Translator/Processes/Helper.cs index 7eaa2b191..97612fc8a 100644 --- a/backend/i18n/translator/Squidex.Translator/Processes/Helper.cs +++ b/backend/i18n/translator/Squidex.Translator/Processes/Helper.cs @@ -30,9 +30,9 @@ namespace Squidex.Translator.Processes Console.WriteLine("----- CHECKING <{0}> -----", locale); var notTranslated = mainTranslations.Keys.Except(texts.Keys).ToList(); - var notRequired = texts.Keys.Except(mainTranslations.Keys).ToList(); + var notUsed = texts.Keys.Except(mainTranslations.Keys).ToList(); - if (notTranslated.Count > 0 || notRequired.Count > 0) + if (notTranslated.Count > 0 || notUsed.Count > 0) { if (notTranslated.Count > 0) { @@ -46,12 +46,12 @@ namespace Squidex.Translator.Processes } } - if (notRequired.Count > 0) + if (notUsed.Count > 0) { Console.WriteLine(); Console.WriteLine("Translations not used:"); - foreach (var key in notRequired.OrderBy(x => x)) + foreach (var key in notUsed.OrderBy(x => x)) { Console.Write(" * "); Console.WriteLine(key); @@ -65,6 +65,33 @@ namespace Squidex.Translator.Processes } } + public static void CleanOtherLocales(TranslationService service) + { + var mainTranslations = service.MainTranslations; + + foreach (var (locale, texts) in service.Translations.Where(x => x.Key != service.MainLocale)) + { + Console.WriteLine(); + Console.WriteLine("----- CLEANING <{0}> -----", locale); + + var notUsed = texts.Keys.Except(mainTranslations.Keys).ToList(); + + if (notUsed.Count > 0) + { + foreach (var unused in notUsed) + { + texts.Remove(unused); + } + + Console.WriteLine("Cleaned {0} translations.", notUsed.Count); + } + else + { + Console.WriteLine("> No errors found"); + } + } + } + public static void CheckUnused(TranslationService service, HashSet translations) { var notUsed = new SortedSet(); diff --git a/backend/src/Migrations/RebuildRunner.cs b/backend/src/Migrations/RebuildRunner.cs index 175dc724a..4f83184e7 100644 --- a/backend/src/Migrations/RebuildRunner.cs +++ b/backend/src/Migrations/RebuildRunner.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.Options; using Migrations.Migrations; using Squidex.Domain.Apps.Entities.Assets; -using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; namespace Migrations @@ -28,11 +27,6 @@ namespace Migrations RebuildFiles rebuildFiles, PopulateGrainIndexes populateGrainIndexes) { - Guard.NotNull(rebuildFiles, nameof(rebuildFiles)); - Guard.NotNull(rebuilder, nameof(rebuilder)); - Guard.NotNull(rebuildOptions, nameof(rebuildOptions)); - Guard.NotNull(populateGrainIndexes, nameof(populateGrainIndexes)); - this.rebuildFiles = rebuildFiles; this.rebuilder = rebuilder; this.rebuildOptions = rebuildOptions.Value; diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/GeoJsonValue.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/GeoJsonValue.cs index d8de7acc9..6cdf281f6 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/GeoJsonValue.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/GeoJsonValue.cs @@ -28,9 +28,7 @@ namespace Squidex.Domain.Apps.Core.Contents { try { - var stream = DefaultPools.MemoryStream.Get(); - - try + using (var stream = DefaultPools.MemoryStream.GetStream()) { serializer.Serialize(value, stream, true); @@ -40,10 +38,6 @@ namespace Squidex.Domain.Apps.Core.Contents return GeoJsonParseResult.Success; } - finally - { - DefaultPools.MemoryStream.Return(stream); - } } catch { diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs index e9dccf8ca..06abc3f27 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs @@ -19,6 +19,8 @@ namespace Squidex.Domain.Apps.Core.Schemas public Instant? MinValue { get; init; } + public string? Format { get; set; } + public DateTimeCalculatedDefaultValue? CalculatedDefaultValue { get; init; } public DateTimeFieldEditor Editor { get; init; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs index 29688c57d..08e332756 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs @@ -24,9 +24,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules public EventEnricher(IMemoryCache userCache, IUserResolver userResolver) { - Guard.NotNull(userCache, nameof(userCache)); - Guard.NotNull(userResolver, nameof(userResolver)); - this.userCache = userCache; this.userResolver = userResolver; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventJsonSchemaGenerator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventJsonSchemaGenerator.cs index b2de217b0..ad9c68637 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventJsonSchemaGenerator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventJsonSchemaGenerator.cs @@ -26,8 +26,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules public EventJsonSchemaGenerator(JsonSchemaGenerator schemaGenerator) { - Guard.NotNull(schemaGenerator, nameof(schemaGenerator)); - this.schemaGenerator = schemaGenerator; schemas = new Lazy>(GenerateSchemas); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventFluidExtensions.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventFluidExtensions.cs index a21bb7a24..9569596dc 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventFluidExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventFluidExtensions.cs @@ -20,8 +20,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Extensions public EventFluidExtensions(IUrlGenerator urlGenerator) { - Guard.NotNull(urlGenerator, nameof(urlGenerator)); - this.urlGenerator = urlGenerator; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventJintExtension.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventJintExtension.cs index 1536ec15a..5589edf3c 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventJintExtension.cs @@ -8,7 +8,6 @@ using Jint.Native; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Scripting; -using Squidex.Infrastructure; using Squidex.Text; namespace Squidex.Domain.Apps.Core.HandleRules.Extensions @@ -20,8 +19,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Extensions public EventJintExtension(IUrlGenerator urlGenerator) { - Guard.NotNull(urlGenerator, nameof(urlGenerator)); - this.urlGenerator = urlGenerator; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/PredefinedPatternsFormatter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/PredefinedPatternsFormatter.cs index 62a739c3a..040f5f484 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/PredefinedPatternsFormatter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/PredefinedPatternsFormatter.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Generic; using System.Globalization; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; -using Squidex.Infrastructure; using Squidex.Shared.Identity; using Squidex.Text; @@ -22,8 +21,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules public PredefinedPatternsFormatter(IUrlGenerator urlGenerator) { - Guard.NotNull(urlGenerator, nameof(urlGenerator)); - this.urlGenerator = urlGenerator; AddPattern("APP_ID", AppId); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs index f02736b59..197875e53 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; -using Squidex.Infrastructure; #pragma warning disable RECS0083 // Shows NotImplementedException throws in the quick task bar @@ -32,8 +31,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules protected RuleActionHandler(RuleEventFormatter formatter) { - Guard.NotNull(formatter, nameof(formatter)); - this.formatter = formatter; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs index 548cc1822..e73b04bd0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs @@ -16,7 +16,6 @@ using NodaTime.Text; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Templates; -using Squidex.Infrastructure; using Squidex.Infrastructure.Json; using Squidex.Text; using ValueTaskSupplement; @@ -72,11 +71,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules public RuleEventFormatter(IJsonSerializer jsonSerializer, IEnumerable formatters, ITemplateEngine templateEngine, IScriptEngine scriptEngine) { - Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); - Guard.NotNull(scriptEngine, nameof(scriptEngine)); - Guard.NotNull(templateEngine, nameof(templateEngine)); - Guard.NotNull(formatters, nameof(formatters)); - this.jsonSerializer = jsonSerializer; this.formatters = formatters; this.templateEngine = templateEngine; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs index 6549b9d0e..913b3aea8 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs @@ -46,15 +46,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules ISemanticLog log, TypeNameRegistry typeNameRegistry) { - Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); - Guard.NotNull(ruleOptions, nameof(ruleOptions)); - Guard.NotNull(ruleTriggerHandlers, nameof(ruleTriggerHandlers)); - Guard.NotNull(ruleActionHandlers, nameof(ruleActionHandlers)); - Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); - Guard.NotNull(eventEnricher, nameof(eventEnricher)); - Guard.NotNull(clock, nameof(clock)); - Guard.NotNull(log, nameof(log)); - this.typeNameRegistry = typeNameRegistry; this.ruleOptions = ruleOptions.Value; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs index 8f25001ad..76f0a9f2f 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs @@ -12,7 +12,6 @@ using Jint; using Jint.Native; using Jint.Native.Json; using Jint.Runtime; -using Squidex.Infrastructure; using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Core.Scripting.Extensions @@ -24,8 +23,6 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions public HttpJintExtension(IHttpClientFactory httpClientFactory) { - Guard.NotNull(httpClientFactory, nameof(httpClientFactory)); - this.httpClientFactory = httpClientFactory; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs index d0c78c238..c2ec4a72a 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs @@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions { try { - return TextHelpers.Html2Text(text); + return text.Html2Text(); } catch { @@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions { try { - return TextHelpers.Markdown2Text(text); + return text.Markdown2Text(); } catch { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/Parser.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/Parser.cs index 0e01d4153..7c6e2ccc5 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/Parser.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/Parser.cs @@ -25,8 +25,6 @@ namespace Squidex.Domain.Apps.Core.Scripting.Internal public Parser(IMemoryCache cache) { - Guard.NotNull(cache, nameof(cache)); - this.cache = cache; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj b/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj index 0eb68127b..4f3e2bd98 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj @@ -17,12 +17,10 @@
- - - + - - + + diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringFluidExtension.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringFluidExtension.cs index 89e7bffb1..47b32b446 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringFluidExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringFluidExtension.cs @@ -39,12 +39,12 @@ namespace Squidex.Domain.Apps.Core.Templates.Extensions private static readonly FilterDelegate Markdown2Text = (input, arguments, context) => { - return FluidValue.Create(TextHelpers.Markdown2Text(input.ToStringValue())); + return FluidValue.Create(input.ToStringValue().Markdown2Text()); }; private static readonly FilterDelegate Html2Text = (input, arguments, context) => { - return FluidValue.Create(TextHelpers.Html2Text(input.ToStringValue())); + return FluidValue.Create(input.ToStringValue().Html2Text()); }; private static readonly FilterDelegate Trim = (input, arguments, context) => diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/FluidTemplateEngine.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/FluidTemplateEngine.cs index d34f4d2de..0b630e123 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/FluidTemplateEngine.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Templates/FluidTemplateEngine.cs @@ -57,8 +57,6 @@ namespace Squidex.Domain.Apps.Core.Templates public FluidTemplateEngine(IEnumerable extensions) { - Guard.NotNull(extensions, nameof(extensions)); - this.extensions = extensions; SquidexTemplate.Setup(extensions); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/TextHelpers.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/TextHelpers.cs deleted file mode 100644 index 033f53d53..000000000 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/TextHelpers.cs +++ /dev/null @@ -1,97 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Text; -using HtmlAgilityPack; -using Markdig; - -namespace Squidex.Domain.Apps.Core -{ - public static class TextHelpers - { - public static string Markdown2Text(string markdown) - { - return Markdown.ToPlainText(markdown).Trim(' ', '\n', '\r'); - } - - public static string Html2Text(string html) - { - var document = LoadHtml(html); - - var sb = new StringBuilder(); - - WriteTextTo(document.DocumentNode, sb); - - return sb.ToString().Trim(' ', '\n', '\r'); - } - - private static HtmlDocument LoadHtml(string text) - { - var document = new HtmlDocument(); - - document.LoadHtml(text); - - return document; - } - - private static void WriteTextTo(HtmlNode node, StringBuilder sb) - { - switch (node.NodeType) - { - case HtmlNodeType.Comment: - break; - case HtmlNodeType.Document: - WriteChildrenTextTo(node, sb); - break; - case HtmlNodeType.Text: - var html = ((HtmlTextNode)node).Text; - - if (HtmlNode.IsOverlappedClosingElement(html)) - { - break; - } - - if (!string.IsNullOrWhiteSpace(html)) - { - sb.Append(HtmlEntity.DeEntitize(html)); - } - - break; - - case HtmlNodeType.Element: - switch (node.Name) - { - case "p": - sb.AppendLine(); - break; - case "br": - sb.AppendLine(); - break; - case "style": - return; - case "script": - return; - } - - if (node.HasChildNodes) - { - WriteChildrenTextTo(node, sb); - } - - break; - } - } - - private static void WriteChildrenTextTo(HtmlNode node, StringBuilder sb) - { - foreach (var child in node.ChildNodes) - { - WriteTextTo(child, sb); - } - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs index a8495a821..727e9a2d0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs @@ -11,6 +11,7 @@ using NodaTime; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.ValidateContent.Validators; using Squidex.Infrastructure.Json.Objects; +using Squidex.Text; namespace Squidex.Domain.Apps.Core.ValidateContent { @@ -203,10 +204,10 @@ namespace Squidex.Domain.Apps.Core.ValidateContent switch (properties.ContentType) { case StringContentType.Markdown: - transform = TextHelpers.Markdown2Text; + transform = MarkdownExtensions.Markdown2Text; break; case StringContentType.Html: - transform = TextHelpers.Html2Text; + transform = HtmlExtensions.Html2Text; break; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs index 092ffb11b..6cc7943eb 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs @@ -20,6 +20,8 @@ namespace Squidex.Domain.Apps.Core.ValidateContent string FileHash { get; } + string MimeType { get; } + string Slug { get; } AssetMetadata Metadata { get; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs index cc709ca20..307d3627f 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -76,12 +76,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators foundIds.Add(asset.AssetId); ValidateCommon(asset, path, addError); + ValidateIsImage(asset, path, addError); - if (asset.Type != AssetType.Image) - { - ValidateNonImage(path, addError); - } - else + if (asset.Type == AssetType.Image) { ValidateImage(asset, path, addError); } @@ -123,9 +120,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators } } - private void ValidateNonImage(ImmutableQueue path, AddError addError) + private void ValidateIsImage(IAssetInfo asset, ImmutableQueue path, AddError addError) { - if (properties.MustBeImage) + if (properties.MustBeImage && asset.Type != AssetType.Image && asset.MimeType != "image/svg+xml") { addError(path, T.Get("contents.validation.image")); } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs index aad1d91a3..7c5f8a4bc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs @@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets var assetFolderEntities = await Collection.Find(filter).SortBy(x => x.FolderName) - .ToListAsync(ct = default); + .ToListAsync(ct); return ResultList.Create(assetFolderEntities.Count, assetFolderEntities); } @@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets var assetFolderEntities = await Collection.Find(filter).Only(x => x.Id) - .ToListAsync(ct = default); + .ToListAsync(ct); var field = Field.Of(x => nameof(x.Id)); @@ -84,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets var assetFolderEntity = await Collection.Find(x => x.DocumentId == documentId && !x.IsDeleted) - .FirstOrDefaultAsync(ct = default); + .FirstOrDefaultAsync(ct); return assetFolderEntity; } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs index 6404fc183..9f7f4ee2f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs @@ -46,11 +46,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { new CreateIndexModel( Index + .Descending(x => x.LastModified) + .Ascending(x => x.Id) .Ascending(x => x.IndexedAppId) .Ascending(x => x.IsDeleted) .Ascending(x => x.ParentId) - .Ascending(x => x.Tags) - .Descending(x => x.LastModified)), + .Ascending(x => x.Tags)), new CreateIndexModel( Index .Ascending(x => x.IndexedAppId) @@ -75,9 +76,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { var find = Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted); - using (var cursor = await find.ToCursorAsync(ct = default)) + using (var cursor = await find.ToCursorAsync(ct)) { - while (await cursor.MoveNextAsync(ct = default)) + while (await cursor.MoveNextAsync(ct)) { foreach (var entity in cursor.Current) { @@ -99,10 +100,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets var filter = BuildFilter(appId, q.Ids.ToHashSet()); var assetEntities = - await Collection.Find(filter).SortByDescending(x => x.LastModified) + await Collection.Find(filter).SortByDescending(x => x.LastModified).ThenBy(x => x.Id) .QueryLimit(q.Query) .QuerySkip(q.Query) - .ToListAsync(ct = default); + .ToListAsync(ct); long assetTotal = assetEntities.Count; if (q.NoTotal) @@ -111,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets } else if (assetEntities.Count >= q.Query.Take || q.Query.Skip > 0) { - assetTotal = await Collection.Find(filter).CountDocumentsAsync(ct = default); + assetTotal = await Collection.Find(filter).CountDocumentsAsync(ct); } return ResultList.Create(assetTotal, assetEntities.OfType()); @@ -127,7 +128,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets .QueryLimit(query) .QuerySkip(query) .QuerySort(query) - .ToListAsync(ct = default); + .ToListAsync(ct); long assetTotal = assetEntities.Count; if (q.NoTotal) @@ -136,7 +137,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets } else if (assetEntities.Count >= q.Query.Take || q.Query.Skip > 0) { - assetTotal = await Collection.Find(filter).CountDocumentsAsync(ct = default); + assetTotal = await Collection.Find(filter).CountDocumentsAsync(ct); } return ResultList.Create(assetTotal, assetEntities); @@ -156,7 +157,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { var assetEntities = await Collection.Find(BuildFilter(appId, ids)).Only(x => x.Id) - .ToListAsync(ct = default); + .ToListAsync(ct); var field = Field.Of(x => nameof(x.Id)); @@ -171,7 +172,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { var assetEntities = await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.ParentId == parentId).Only(x => x.Id) - .ToListAsync(ct = default); + .ToListAsync(ct); var field = Field.Of(x => nameof(x.Id)); @@ -186,7 +187,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { var assetEntity = await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.FileHash == hash && x.FileName == fileName && x.FileSize == fileSize) - .FirstOrDefaultAsync(ct = default); + .FirstOrDefaultAsync(ct); return assetEntity; } @@ -199,7 +200,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { var assetEntity = await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.Slug == slug) - .FirstOrDefaultAsync(ct = default); + .FirstOrDefaultAsync(ct); return assetEntity; } @@ -214,7 +215,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets var assetEntity = await Collection.Find(x => x.DocumentId == documentId && !x.IsDeleted) - .FirstOrDefaultAsync(ct = default); + .FirstOrDefaultAsync(ct); return assetEntity; } @@ -227,7 +228,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { var assetEntity = await Collection.Find(x => x.Id == id && !x.IsDeleted) - .FirstOrDefaultAsync(ct = default); + .FirstOrDefaultAsync(ct); return assetEntity; } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs index 1bde3b56b..cf40963ed 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs @@ -42,6 +42,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors { var filters = new List> { + Filter.Exists(x => x.LastModified), + Filter.Exists(x => x.Id), Filter.Eq(x => x.IndexedAppId, appId) }; diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs index 471295b03..c67f74346 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; @@ -33,9 +34,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents private readonly QueryReferrers queryReferrers; private readonly QueryScheduled queryScheduled; private readonly string name; - private readonly bool useWildcardIndex; + private readonly ReadPreference readPreference; - public MongoContentCollection(string name, IMongoDatabase database, IAppProvider appProvider, bool useWildcardIndex) + public MongoContentCollection(string name, IMongoDatabase database, IAppProvider appProvider, ReadPreference readPreference) : base(database) { this.name = name; @@ -48,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents queryReferrers = new QueryReferrers(); queryScheduled = new QueryScheduled(); - this.useWildcardIndex = useWildcardIndex; + this.readPreference = readPreference; } public IMongoCollection GetInternalCollection() @@ -61,26 +62,34 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents return name; } - protected override async Task SetupCollectionAsync(IMongoCollection collection, + protected override MongoCollectionSettings CollectionSettings() + { + return new MongoCollectionSettings + { + ReadPreference = readPreference + }; + } + + protected override Task SetupCollectionAsync(IMongoCollection collection, CancellationToken ct) { - if (useWildcardIndex) + var operations = new OperationBase[] + { + queryAsStream, + queryBdId, + queryByIds, + queryByQuery, + queryReferences, + queryReferrers, + queryScheduled + }; + + foreach (var operation in operations) { - await collection.Indexes.CreateOneAsync( - new CreateIndexModel( - Index.Wildcard() - ), null, ct); + operation.Setup(collection); } - var skipIndex = useWildcardIndex; - - await queryAsStream.PrepareAsync(collection, skipIndex, ct); - await queryBdId.PrepareAsync(collection, skipIndex, ct); - await queryByIds.PrepareAsync(collection, skipIndex, ct); - await queryByQuery.PrepareAsync(collection, skipIndex, ct); - await queryReferences.PrepareAsync(collection, skipIndex, ct); - await queryReferrers.PrepareAsync(collection, skipIndex, ct); - await queryScheduled.PrepareAsync(collection, skipIndex, ct); + return collection.Indexes.CreateManyAsync(operations.SelectMany(x => x.CreateIndexes()), ct); } public Task ResetScheduledAsync(DomainId documentId, diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs index fd4236e36..51ea67dea 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs @@ -34,17 +34,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents TypeConverterStringSerializer.Register(); } - public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, bool useWildcardIndex) + public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider) { - Guard.NotNull(appProvider, nameof(appProvider)); - collectionAll = - new MongoContentCollection( - "States_Contents_All3", database, appProvider, useWildcardIndex); + new MongoContentCollection("States_Contents_All3", database, appProvider, + ReadPreference.Primary); collectionPublished = - new MongoContentCollection( - "States_Contents_Published3", database, appProvider, useWildcardIndex); + new MongoContentCollection("States_Contents_Published3", database, appProvider, + ReadPreference.Secondary); this.appProvider = appProvider; } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/OperationBase.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/OperationBase.cs index 6f9facfc1..d34a78630 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/OperationBase.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/OperationBase.cs @@ -5,8 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Threading; -using System.Threading.Tasks; +using System.Collections.Generic; using MongoDB.Driver; namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations @@ -21,19 +20,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations public IMongoCollection Collection { get; private set; } - public async Task PrepareAsync(IMongoCollection collection, bool skipIndex, CancellationToken ct = default) + public void Setup(IMongoCollection collection) { Collection = collection; - - if (!skipIndex) - { - await PrepareAsync(ct); - } } - protected virtual Task PrepareAsync(CancellationToken ct) + public virtual IEnumerable> CreateIndexes() { - return Task.CompletedTask; + yield break; } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs index f2bf88040..ac2a0106e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; -using System.Threading.Tasks; using MongoDB.Driver; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Infrastructure; @@ -17,15 +16,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations { public sealed class QueryAsStream : OperationBase { - protected override async Task PrepareAsync(CancellationToken ct) + public override IEnumerable> CreateIndexes() { - var indexBySchema = - new CreateIndexModel(Index - .Ascending(x => x.IndexedAppId) - .Ascending(x => x.IsDeleted) - .Ascending(x => x.IndexedSchemaId)); - - await Collection.Indexes.CreateOneAsync(indexBySchema, cancellationToken: ct); + yield return new CreateIndexModel(Index + .Ascending(x => x.IndexedAppId) + .Ascending(x => x.IsDeleted) + .Ascending(x => x.IndexedSchemaId)); } public async IAsyncEnumerable StreamAll(DomainId appId, HashSet? schemaIds, diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs index 7ef366378..8cd169c94 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs @@ -41,25 +41,20 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations this.appProvider = appProvider; } - protected override async Task PrepareAsync(CancellationToken ct) + public override IEnumerable> CreateIndexes() { - var indexBySchemaWithRefs = - new CreateIndexModel(Index - .Ascending(x => x.IndexedAppId) - .Ascending(x => x.IndexedSchemaId) - .Ascending(x => x.IsDeleted) - .Ascending(x => x.ReferencedIds) - .Descending(x => x.LastModified)); - - await Collection.Indexes.CreateOneAsync(indexBySchemaWithRefs, cancellationToken: ct); - - var indexBySchema = - new CreateIndexModel(Index - .Ascending(x => x.IndexedSchemaId) - .Ascending(x => x.IsDeleted) - .Descending(x => x.LastModified)); - - await Collection.Indexes.CreateOneAsync(indexBySchema, cancellationToken: ct); + yield return new CreateIndexModel(Index + .Descending(x => x.LastModified) + .Ascending(x => x.Id) + .Ascending(x => x.IndexedAppId) + .Ascending(x => x.IndexedSchemaId) + .Ascending(x => x.IsDeleted) + .Ascending(x => x.ReferencedIds)); + + yield return new CreateIndexModel(Index + .Ascending(x => x.IndexedSchemaId) + .Ascending(x => x.IsDeleted) + .Descending(x => x.LastModified)); } public async Task> QueryIdsAsync(DomainId appId, DomainId schemaId, FilterNode filterNode, @@ -202,13 +197,20 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations private static bool IsSatisfiedByIndex(ClrQuery query) { - return query.Sort?.All(x => x.Path.ToString() == "mt" && x.Order == SortOrder.Descending) == true; + return query.Sort != null && + query.Sort.Count == 2 && + query.Sort[0].Path.ToString() == "mt" && + query.Sort[0].Order == SortOrder.Descending && + query.Sort[1].Path.ToString() == "id" && + query.Sort[1].Order == SortOrder.Ascending; } private static FilterDefinition BuildFilter(DomainId appId, DomainId schemaId, FilterNode? filter) { var filters = new List> { + Filter.Exists(x => x.LastModified), + Filter.Exists(x => x.Id), Filter.Eq(x => x.IndexedAppId, appId), Filter.Eq(x => x.IndexedSchemaId, schemaId) }; @@ -231,6 +233,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations { var filters = new List> { + Filter.Exists(x => x.LastModified), + Filter.Exists(x => x.Id), Filter.Eq(x => x.IndexedAppId, appId), Filter.In(x => x.IndexedSchemaId, schemaIds), }; diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrers.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrers.cs index 9d2e519c6..af19460fa 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrers.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrers.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; @@ -15,15 +16,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations { internal sealed class QueryReferrers : OperationBase { - protected override Task PrepareAsync(CancellationToken ct) + public override IEnumerable> CreateIndexes() { - var index = - new CreateIndexModel(Index - .Ascending(x => x.ReferencedIds) - .Ascending(x => x.IndexedAppId) - .Ascending(x => x.IsDeleted)); - - return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct); + yield return new CreateIndexModel(Index + .Ascending(x => x.ReferencedIds) + .Ascending(x => x.IndexedAppId) + .Ascending(x => x.IsDeleted)); } public async Task CheckExistsAsync(DomainId appId, DomainId contentId, diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduled.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduled.cs index 5970040b6..fcebadb09 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduled.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduled.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; @@ -18,14 +19,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations { internal sealed class QueryScheduled : OperationBase { - protected override Task PrepareAsync(CancellationToken ct) + public override IEnumerable> CreateIndexes() { - var index = - new CreateIndexModel(Index - .Ascending(x => x.ScheduledAt) - .Ascending(x => x.IsDeleted)); - - return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct); + yield return new CreateIndexModel(Index + .Ascending(x => x.ScheduledAt) + .Ascending(x => x.IsDeleted)); } public Task QueryAsync(Instant now, Func callback, diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoTextIndex.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoTextIndex.cs index eb975f0cb..e5ec59318 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoTextIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoTextIndex.cs @@ -82,7 +82,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.FullText public async Task?> SearchAsync(IAppEntity app, GeoQuery query, SearchScope scope) { var byGeo = - await Collection.Find( + await GetCollection(scope).Find( Filter.And( Filter.Eq(x => x.AppId, app.Id), Filter.Eq(x => x.SchemaId, query.SchemaId), @@ -126,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.FullText private async Task> SearchBySchemaAsync(string queryText, IAppEntity app, TextFilter filter, SearchScope scope, int limit) { var bySchema = - await Collection.Find( + await GetCollection(scope).Find( Filter.And( Filter.Eq(x => x.AppId, app.Id), Filter.In(x => x.SchemaId, filter.SchemaIds), @@ -143,7 +143,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.FullText private async Task> SearchByAppAsync(string queryText, IAppEntity app, SearchScope scope, int limit) { var bySchema = - await Collection.Find( + await GetCollection(scope).Find( Filter.And( Filter.Eq(x => x.AppId, app.Id), Filter.Exists(x => x.SchemaId), @@ -168,5 +168,17 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.FullText return Filter.Eq(x => x.ServePublished, true); } } + + private IMongoCollection GetCollection(SearchScope scope) + { + if (scope == SearchScope.All) + { + return Collection; + } + else + { + return Collection.WithReadPreference(ReadPreference.Secondary); + } + } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs index bb75dc070..3859ed3fb 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs @@ -39,7 +39,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules protected override async Task SetupCollectionAsync(IMongoCollection collection, CancellationToken ct = default) { - await statisticsCollection.InitializeAsync(ct = default); + await statisticsCollection.InitializeAsync(ct); await collection.Indexes.CreateManyAsync(new[] { @@ -73,12 +73,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules filter = Filter.And(filter, Filter.Eq(x => x.RuleId, ruleId.Value)); } - var ruleEventEntities = await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.Created).ToListAsync(ct = default); + var ruleEventEntities = await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.Created).ToListAsync(ct); var ruleEventTotal = (long)ruleEventEntities.Count; if (ruleEventTotal >= take || skip > 0) { - ruleEventTotal = await Collection.Find(filter).CountDocumentsAsync(ct = default); + ruleEventTotal = await Collection.Find(filter).CountDocumentsAsync(ct); } return ResultList.Create(ruleEventTotal, ruleEventEntities); @@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules { var ruleEvent = await Collection.Find(x => x.DocumentId == id) - .FirstOrDefaultAsync(ct = default); + .FirstOrDefaultAsync(ct); return ruleEvent; } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj index 8af1e445c..cc50ada9c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj @@ -17,7 +17,7 @@ - + diff --git a/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs b/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs index 34243d69f..315961928 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs @@ -31,11 +31,6 @@ namespace Squidex.Domain.Apps.Entities public AppProvider(ILocalCache localCache, IAppsIndex indexForApps, IRulesIndex indexRules, ISchemasIndex indexSchemas) { - Guard.NotNull(indexForApps, nameof(indexForApps)); - Guard.NotNull(indexRules, nameof(indexRules)); - Guard.NotNull(indexSchemas, nameof(indexSchemas)); - Guard.NotNull(localCache, nameof(localCache)); - this.localCache = localCache; this.indexForApps = indexForApps; this.indexRules = indexRules; diff --git a/backend/src/Squidex.Domain.Apps.Entities/AppTag.cs b/backend/src/Squidex.Domain.Apps.Entities/AppTag.cs new file mode 100644 index 000000000..5e8a5cca3 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/AppTag.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Fluid.Tags; +using Microsoft.Extensions.DependencyInjection; + +namespace Squidex.Domain.Apps.Entities +{ + internal abstract class AppTag : ArgumentsTag + { + protected IAppProvider AppProvider { get; } + + protected AppTag(IServiceProvider serviceProvider) + { + AppProvider = serviceProvider.GetRequiredService(); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs index 17138f2f7..117c3a2d1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs @@ -22,8 +22,6 @@ namespace Squidex.Domain.Apps.Entities.Apps public AppSettingsSearchSource(IUrlGenerator urlGenerator) { - Guard.NotNull(urlGenerator, nameof(urlGenerator)); - this.urlGenerator = urlGenerator; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs index 4fbfbbcc7..2a39b7427 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs @@ -19,8 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Apps public AppUISettings(IGrainFactory grainFactory) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - this.grainFactory = grainFactory; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs index ea6c18642..cd943b1ab 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs @@ -27,8 +27,6 @@ namespace Squidex.Domain.Apps.Entities.Apps public AppUISettingsGrain(IGrainState state) { - Guard.NotNull(state, nameof(state)); - this.state = state; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs index 7d6a1d6d1..89325c8f3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs @@ -32,10 +32,6 @@ namespace Squidex.Domain.Apps.Entities.Apps public BackupApps(IAppImageStore appImageStore, IAppsIndex appsIndex, IAppUISettings appUISettings) { - Guard.NotNull(appImageStore, nameof(appImageStore)); - Guard.NotNull(appsIndex, nameof(appsIndex)); - Guard.NotNull(appUISettings, nameof(appUISettings)); - this.appsIndex = appsIndex; this.appImageStore = appImageStore; this.appUISettings = appUISettings; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppImageStore.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppImageStore.cs index 94b4be2da..ddf8f95e4 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppImageStore.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppImageStore.cs @@ -19,8 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Apps public DefaultAppImageStore(IAssetStore assetStore) { - Guard.NotNull(assetStore, nameof(assetStore)); - this.assetStore = assetStore; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs index 3ce41bc53..c678f6cf7 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs @@ -41,8 +41,6 @@ namespace Squidex.Domain.Apps.Entities.Apps public DefaultAppLogStore(IRequestLogStore requestLogStore) { - Guard.NotNull(requestLogStore, nameof(requestLogStore)); - this.requestLogStore = requestLogStore; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs index 2ba4138f9..9a61bbcd7 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.Diagnostics.HealthChecks; using Orleans; using Squidex.Domain.Apps.Entities.Apps.Indexes; -using Squidex.Infrastructure; using Squidex.Infrastructure.Orleans; namespace Squidex.Domain.Apps.Entities.Apps.Diagnostics @@ -21,8 +20,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Diagnostics public OrleansAppsHealthCheck(IGrainFactory grainFactory) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - this.grainFactory = grainFactory; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppCommandMiddleware.cs index 9317e2281..b24400e06 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppCommandMiddleware.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Orleans; using Squidex.Assets; using Squidex.Domain.Apps.Entities.Apps.Commands; -using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; @@ -29,10 +28,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject IContextProvider contextProvider) : base(grainFactory) { - Guard.NotNull(contextProvider, nameof(contextProvider)); - Guard.NotNull(appImageStore, nameof(appImageStore)); - Guard.NotNull(assetThumbnailGenerator, nameof(assetThumbnailGenerator)); - this.appImageStore = appImageStore; this.assetThumbnailGenerator = assetThumbnailGenerator; this.contextProvider = contextProvider; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs index 64e39e953..107896161 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs @@ -38,11 +38,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject IUserResolver userResolver) : base(persistence, log) { - Guard.NotNull(initialPatterns, nameof(initialPatterns)); - Guard.NotNull(userResolver, nameof(userResolver)); - Guard.NotNull(appPlansProvider, nameof(appPlansProvider)); - Guard.NotNull(appPlansBillingManager, nameof(appPlansBillingManager)); - this.userResolver = userResolver; this.appPlansProvider = appPlansProvider; this.appPlansBillingManager = appPlansBillingManager; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs index a1ca8c570..995007747 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs @@ -33,9 +33,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes public AppsIndex(IGrainFactory grainFactory, IReplicatedCache grainCache) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - Guard.NotNull(grainCache, nameof(grainCache)); - this.grainFactory = grainFactory; this.grainCache = grainCache; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEventConsumer.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEventConsumer.cs index b76038828..9b60f4ec9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEventConsumer.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEventConsumer.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using NodaTime; using Squidex.Domain.Apps.Entities.Notifications; using Squidex.Domain.Apps.Events.Apps; -using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Log; using Squidex.Shared.Users; @@ -35,10 +34,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation public InvitationEventConsumer(INotificationSender emailSender, IUserResolver userResolver, ISemanticLog log) { - Guard.NotNull(emailSender, nameof(emailSender)); - Guard.NotNull(userResolver, nameof(userResolver)); - Guard.NotNull(log, nameof(log)); - this.emailSender = emailSender; this.userResolver = userResolver; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs index 2aa9912ef..9d2653d7f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs @@ -19,8 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation public InviteUserCommandMiddleware(IUserResolver userResolver) { - Guard.NotNull(userResolver, nameof(userResolver)); - this.userResolver = userResolver; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/ConfigAppPlansProvider.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/ConfigAppPlansProvider.cs index 3a039ca46..f0325e0dc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/ConfigAppPlansProvider.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/ConfigAppPlansProvider.cs @@ -30,8 +30,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans public ConfigAppPlansProvider(IEnumerable config) { - Guard.NotNull(config, nameof(config)); - foreach (var plan in config.OrderBy(x => x.MaxApiCalls).Select(x => x.Clone())) { plansList.Add(plan); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageGate.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageGate.cs index a65da64fd..2975b9c37 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageGate.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageGate.cs @@ -28,10 +28,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans public UsageGate(IAppPlansProvider appPlansProvider, IApiUsageTracker apiUsageTracker, IGrainFactory grainFactory) { - Guard.NotNull(apiUsageTracker, nameof(apiUsageTracker)); - Guard.NotNull(appPlansProvider, nameof(appPlansProvider)); - Guard.NotNull(grainFactory, nameof(grainFactory)); - this.appPlansProvider = appPlansProvider; this.apiUsageTracker = apiUsageTracker; this.grainFactory = grainFactory; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageNotifierGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageNotifierGrain.cs index 7c2029816..2e0e480a0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageNotifierGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageNotifierGrain.cs @@ -34,11 +34,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans public UsageNotifierGrain(IGrainState state, INotificationSender notificationSender, IUserResolver userResolver, IClock clock) { - Guard.NotNull(state, nameof(state)); - Guard.NotNull(notificationSender, nameof(notificationSender)); - Guard.NotNull(userResolver, nameof(userResolver)); - Guard.NotNull(clock, nameof(clock)); - this.state = state; this.notificationSender = notificationSender; this.userResolver = userResolver; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs index d3a4863f7..23b355719 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; -using Squidex.Infrastructure; using Squidex.Infrastructure.Security; using Squidex.Shared; @@ -28,8 +27,6 @@ namespace Squidex.Domain.Apps.Entities.Apps public RolePermissionsProvider(IAppProvider appProvider) { - Guard.NotNull(appProvider, nameof(appProvider)); - this.appProvider = appProvider; foreach (var field in typeof(Permissions).GetFields(BindingFlags.Public | BindingFlags.Static)) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs index 5799be4cd..eaa637d13 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs @@ -23,8 +23,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates public TemplateCommandMiddleware(IEnumerable templates) { - Guard.NotNull(templates, nameof(templates)); - this.templates = templates.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs index a247b8dfe..200c49e09 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs @@ -16,7 +16,6 @@ using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Assets; -using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -37,10 +36,6 @@ namespace Squidex.Domain.Apps.Entities.Assets IAssetLoader assetLoader, IAssetRepository assetRepository) { - Guard.NotNull(scriptEngine, nameof(scriptEngine)); - Guard.NotNull(assetLoader, nameof(assetLoader)); - Guard.NotNull(assetRepository, nameof(assetRepository)); - this.scriptEngine = scriptEngine; this.assetLoader = assetLoader; this.assetRepository = assetRepository; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetExtensions.cs index 44feae209..6e839fa66 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetExtensions.cs @@ -5,6 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; +using System.Text; +using System.Threading.Tasks; +using Squidex.Infrastructure; +using Squidex.Infrastructure.ObjectPool; + namespace Squidex.Domain.Apps.Entities.Assets { public static class AssetExtensions @@ -20,5 +26,29 @@ namespace Squidex.Domain.Apps.Entities.Assets { return builder.WithBoolean(HeaderNoEnrichment, value); } + + public static async Task GetTextAsync(this IAssetFileStore assetFileStore, DomainId appId, DomainId id, long fileVersion, string? encoding) + { + using (var stream = DefaultPools.MemoryStream.GetStream()) + { + await assetFileStore.DownloadAsync(appId, id, fileVersion, null, stream); + + stream.Position = 0; + + var bytes = stream.ToArray(); + + switch (encoding?.ToLowerInvariant()) + { + case "base64": + return Convert.ToBase64String(bytes); + case "ascii": + return Encoding.ASCII.GetString(bytes); + case "unicode": + return Encoding.Unicode.GetString(bytes); + default: + return Encoding.UTF8.GetString(bytes); + } + } + } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetOptions.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetOptions.cs index 099a24d90..89807945b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetOptions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetOptions.cs @@ -5,14 +5,22 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; + namespace Squidex.Domain.Apps.Entities.Assets { public sealed class AssetOptions { + public bool FolderPerApp { get; set; } = false; + public int DefaultPageSize { get; set; } = 200; public int MaxResults { get; set; } = 200; public long MaxSize { get; set; } = 5 * 1024 * 1024; + + public TimeSpan TimeoutFind { get; set; } = TimeSpan.FromSeconds(1); + + public TimeSpan TimeoutQuery { get; set; } = TimeSpan.FromSeconds(5); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetPermanentDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetPermanentDeleter.cs index cb2b4b28f..7d2b1ed7a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetPermanentDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetPermanentDeleter.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using Squidex.Assets; using Squidex.Domain.Apps.Events.Assets; -using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -31,8 +30,6 @@ namespace Squidex.Domain.Apps.Entities.Assets public AssetPermanentDeleter(IAssetFileStore assetFileStore, TypeNameRegistry typeNameRegistry) { - Guard.NotNull(assetFileStore, nameof(assetFileStore)); - this.assetFileStore = assetFileStore; deletedType = typeNameRegistry?.GetName(); @@ -56,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Assets { try { - await assetFileStore.DeleteAsync(assetDeleted.AppId.Id, assetDeleted.AssetId, version); + await assetFileStore.DeleteAsync(assetDeleted.AppId.Id, assetDeleted.AssetId, version, null); } catch (AssetNotFoundException) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs index 58c4d972e..5d5f190d2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs @@ -24,8 +24,6 @@ namespace Squidex.Domain.Apps.Entities.Assets public AssetUsageTracker(IUsageTracker usageTracker) { - Guard.NotNull(usageTracker, nameof(usageTracker)); - this.usageTracker = usageTracker; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsFluidExtension.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsFluidExtension.cs index 9dfe59913..8240a23ca 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsFluidExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsFluidExtension.cs @@ -11,74 +11,55 @@ using System.Text.Encodings.Web; using System.Threading.Tasks; using Fluid; using Fluid.Ast; -using Fluid.Tags; +using Fluid.Values; using GraphQL.Utilities; using Microsoft.Extensions.DependencyInjection; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Templates; using Squidex.Domain.Apps.Core.ValidateContent; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Assets { public sealed class AssetsFluidExtension : IFluidExtension { + private static readonly FluidValue ErrorNullAsset = FluidValue.Create(null); + private static readonly FluidValue ErrorNoAsset = new StringValue("NoAsset"); + private static readonly FluidValue ErrorTooBig = new StringValue("ErrorTooBig"); private readonly IServiceProvider serviceProvider; - private sealed class AssetTag : ArgumentsTag + private sealed class AssetTag : AppTag { - private readonly IServiceProvider serviceProvider; + private readonly IAssetQueryService assetQuery; public AssetTag(IServiceProvider serviceProvider) + : base(serviceProvider) { - this.serviceProvider = serviceProvider; + assetQuery = serviceProvider.GetRequiredService(); } public override async ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context, FilterArgument[] arguments) { if (arguments.Length == 2 && context.GetValue("event")?.ToObjectValue() is EnrichedEvent enrichedEvent) { - var app = await GetAppAsync(enrichedEvent); + var id = await arguments[1].Expression.EvaluateAsync(context); - if (app == null) - { - return Completion.Normal; - } - - var requestContext = - Context.Admin(app).Clone(b => b - .WithoutTotal()); - - var id = (await arguments[1].Expression.EvaluateAsync(context)).ToStringValue(); + var content = await ResolveAssetAsync(AppProvider, assetQuery, enrichedEvent.AppId.Id, id); - var assetQuery = serviceProvider.GetRequiredService(); - - var asset = await assetQuery.FindAsync(requestContext, DomainId.Create(id)); - - if (asset != null) + if (content != null) { var name = (await arguments[0].Expression.EvaluateAsync(context)).ToStringValue(); - context.SetValue(name, asset); + context.SetValue(name, content); } } return Completion.Normal; } - - private Task GetAppAsync(EnrichedEvent enrichedEvent) - { - var appProvider = serviceProvider.GetRequiredService(); - - return appProvider.GetAppAsync(enrichedEvent.AppId.Id, false); - } } public AssetsFluidExtension(IServiceProvider serviceProvider) { - Guard.NotNull(serviceProvider, nameof(serviceProvider)); - this.serviceProvider = serviceProvider; } @@ -91,11 +72,95 @@ namespace Squidex.Domain.Apps.Entities.Assets memberAccessStrategy.Register(); memberAccessStrategy.Register(); memberAccessStrategy.Register(); + + AddAssetFilter(); + AddAssetTextFilter(); + } + + private void AddAssetFilter() + { + var appProvider = serviceProvider.GetRequiredService(); + + var assetQuery = serviceProvider.GetRequiredService(); + + TemplateContext.GlobalFilters.AddAsyncFilter("asset", async (input, arguments, context) => + { + if (context.GetValue("event")?.ToObjectValue() is EnrichedEvent enrichedEvent) + { + var asset = await ResolveAssetAsync(appProvider, assetQuery, enrichedEvent.AppId.Id, input); + + if (asset == null) + { + return ErrorNullAsset; + } + + return FluidValue.Create(asset); + } + + return ErrorNullAsset; + }); + } + + private void AddAssetTextFilter() + { + var assetFileStore = serviceProvider.GetRequiredService(); + + TemplateContext.GlobalFilters.AddAsyncFilter("assetText", async (input, arguments, context) => + { + if (input is not ObjectValue objectValue) + { + return ErrorNoAsset; + } + + async Task ResolveAssetText(DomainId appId, DomainId id, long fileSize, long fileVersion) + { + if (fileSize > 256_000) + { + return ErrorTooBig; + } + + var encoding = arguments.At(0).ToStringValue()?.ToUpperInvariant(); + var encoded = await assetFileStore.GetTextAsync(appId, id, fileVersion, encoding); + + return new StringValue(encoded); + } + + switch (objectValue.ToObjectValue()) + { + case IAssetEntity asset: + return await ResolveAssetText(asset.AppId.Id, asset.Id, asset.FileSize, asset.FileVersion); + + case EnrichedAssetEvent @event: + return await ResolveAssetText(@event.AppId.Id, @event.Id, @event.FileSize, @event.FileVersion); + } + + return ErrorNoAsset; + }); } public void RegisterLanguageExtensions(FluidParserFactory factory) { factory.RegisterTag("asset", new AssetTag(serviceProvider)); } + + private static async Task ResolveAssetAsync(IAppProvider appProvider, IAssetQueryService assetQuery, DomainId appId, FluidValue id) + { + var app = await appProvider.GetAppAsync(appId); + + if (app == null) + { + return null; + } + + var domainId = DomainId.Create(id.ToStringValue()); + + var requestContext = + Context.Admin(app).Clone(b => b + .WithoutTotal()); + + var asset = await assetQuery.FindAsync(requestContext, domainId); + + return asset; + } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs index 5682f2dcb..9e331a1d1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs @@ -12,7 +12,9 @@ using System.Security.Claims; using System.Threading.Tasks; using Jint.Native; using Jint.Runtime; +using Jint.Runtime.Interop; using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure; @@ -23,16 +25,21 @@ namespace Squidex.Domain.Apps.Entities.Assets public sealed class AssetsJintExtension : IJintExtension { private delegate void GetAssetsDelegate(JsValue references, Action callback); + private delegate void GetAssetTextDelegate(JsValue references, Action callback, JsValue encoding); private readonly IServiceProvider serviceProvider; public AssetsJintExtension(IServiceProvider serviceProvider) { - Guard.NotNull(serviceProvider, nameof(serviceProvider)); - this.serviceProvider = serviceProvider; } public void ExtendAsync(ExecutionContext context) + { + AddAssetText(context); + AddAsset(context); + } + + private void AddAsset(ExecutionContext context) { if (!context.TryGetValue(nameof(ScriptVars.AppId), out var appId)) { @@ -50,6 +57,66 @@ namespace Squidex.Domain.Apps.Entities.Assets context.Engine.SetValue("getAssets", action); } + private void AddAssetText(ExecutionContext context) + { + var action = new GetAssetTextDelegate((references, callback, encoding) => GetText(context, references, callback, encoding)); + + context.Engine.SetValue("getAssetText", action); + } + + private void GetText(ExecutionContext context, JsValue input, Action callback, JsValue encoding) + { + GetTextAsync(context, input, callback, encoding).Forget(); + } + + private async Task GetTextAsync(ExecutionContext context, JsValue input, Action callback, JsValue encoding) + { + Guard.NotNull(callback, nameof(callback)); + + if (input is not ObjectWrapper objectWrapper) + { + callback(JsValue.FromObject(context.Engine, "ErrorNoAsset")); + return; + } + + async Task ResolveAssetText(DomainId appId, DomainId id, long fileSize, long fileVersion) + { + if (fileSize > 256_000) + { + callback(JsValue.FromObject(context.Engine, "ErrorTooBig")); + return; + } + + context.MarkAsync(); + + try + { + var assetFileStore = serviceProvider.GetRequiredService(); + + var encoded = await assetFileStore.GetTextAsync(appId, id, fileVersion, encoding?.ToString()); + + callback(JsValue.FromObject(context.Engine, encoded)); + } + catch (Exception ex) + { + context.Fail(ex); + } + } + + switch (objectWrapper.Target) + { + case IAssetEntity asset: + await ResolveAssetText(asset.AppId.Id, asset.Id, asset.FileSize, asset.FileVersion); + return; + + case EnrichedAssetEvent @event: + await ResolveAssetText(@event.AppId.Id, @event.Id, @event.FileSize, @event.FileVersion); + return; + } + + callback(JsValue.FromObject(context.Engine, "ErrorNoAsset")); + } + private void GetAssets(ExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action callback) { GetReferencesAsync(context, appId, user, references, callback).Forget(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsSearchSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsSearchSource.cs index 3b1dcceb2..234194999 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsSearchSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsSearchSource.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Search; -using Squidex.Infrastructure; using Squidex.Infrastructure.Queries; using Squidex.Shared; @@ -23,9 +22,6 @@ namespace Squidex.Domain.Apps.Entities.Assets public AssetsSearchSource(IAssetQueryService assetQuery, IUrlGenerator urlGenerator) { - Guard.NotNull(assetQuery, nameof(assetQuery)); - Guard.NotNull(urlGenerator, nameof(urlGenerator)); - this.assetQuery = assetQuery; this.urlGenerator = urlGenerator; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs index cd5b8bd6a..1e5705af1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs @@ -33,10 +33,6 @@ namespace Squidex.Domain.Apps.Entities.Assets public BackupAssets(Rebuilder rebuilder, IAssetFileStore assetFileStore, ITagService tagService) { - Guard.NotNull(rebuilder, nameof(rebuilder)); - Guard.NotNull(assetFileStore, nameof(assetFileStore)); - Guard.NotNull(tagService, nameof(tagService)); - this.rebuilder = rebuilder; this.assetFileStore = assetFileStore; this.tagService = tagService; @@ -131,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Assets { await writer.WriteBlobAsync(GetName(assetId, fileVersion), stream => { - return assetFileStore.DownloadAsync(appId, assetId, fileVersion, stream); + return assetFileStore.DownloadAsync(appId, assetId, fileVersion, null, stream); }); } catch (AssetNotFoundException) @@ -146,7 +142,7 @@ namespace Squidex.Domain.Apps.Entities.Assets { await reader.ReadBlobAsync(GetName(assetId, fileVersion), stream => { - return assetFileStore.UploadAsync(appId, assetId, fileVersion, stream); + return assetFileStore.UploadAsync(appId, assetId, fileVersion, null, stream); }); } catch (FileNotFoundException) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DefaultAssetFileStore.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DefaultAssetFileStore.cs index 1aa7a0010..e34b8cbc1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DefaultAssetFileStore.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DefaultAssetFileStore.cs @@ -8,6 +8,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Options; using Squidex.Assets; using Squidex.Infrastructure; @@ -16,59 +17,63 @@ namespace Squidex.Domain.Apps.Entities.Assets public sealed class DefaultAssetFileStore : IAssetFileStore { private readonly IAssetStore assetStore; + private readonly AssetOptions options; - public DefaultAssetFileStore(IAssetStore assetStore) + public DefaultAssetFileStore(IAssetStore assetStore, + IOptions options) { this.assetStore = assetStore; + + this.options = options.Value; } - public string? GeneratePublicUrl(DomainId appId, DomainId id, long fileVersion) + public string? GeneratePublicUrl(DomainId appId, DomainId id, long fileVersion, string? suffix) { - var fileName = GetFileName(appId, id, fileVersion); + var fileName = GetFileName(appId, id, fileVersion, suffix); return assetStore.GeneratePublicUrl(fileName); } - public async Task GetFileSizeAsync(DomainId appId, DomainId id, long fileVersion, + public async Task GetFileSizeAsync(DomainId appId, DomainId id, long fileVersion, string? suffix, CancellationToken ct = default) { try { - var fileNameNew = GetFileName(appId, id, fileVersion); + var fileNameNew = GetFileName(appId, id, fileVersion, suffix); return await assetStore.GetSizeAsync(fileNameNew, ct); } - catch (AssetNotFoundException) + catch (AssetNotFoundException) when (!options.FolderPerApp) { - var fileNameOld = GetFileName(id, fileVersion); + var fileNameOld = GetFileName(id, fileVersion, suffix); return await assetStore.GetSizeAsync(fileNameOld, ct); } } - public async Task DownloadAsync(DomainId appId, DomainId id, long fileVersion, Stream stream, BytesRange range = default, + public async Task DownloadAsync(DomainId appId, DomainId id, long fileVersion, string? suffix, Stream stream, BytesRange range = default, CancellationToken ct = default) { try { - var fileNameNew = GetFileName(appId, id, fileVersion); + var fileNameNew = GetFileName(appId, id, fileVersion, suffix); await assetStore.DownloadAsync(fileNameNew, stream, range, ct); } - catch (AssetNotFoundException) + catch (AssetNotFoundException) when (!options.FolderPerApp) { - var fileNameOld = GetFileName(id, fileVersion); + var fileNameOld = GetFileName(id, fileVersion, suffix); await assetStore.DownloadAsync(fileNameOld, stream, range, ct); } } - public Task UploadAsync(DomainId appId, DomainId id, long fileVersion, Stream stream, + public Task UploadAsync(DomainId appId, DomainId id, long fileVersion, string? suffix, Stream stream, bool overwrite = true, CancellationToken ct = default) { - var fileName = GetFileName(appId, id, fileVersion); + var fileName = GetFileName(appId, id, fileVersion, suffix); - return assetStore.UploadAsync(fileName, stream, true, ct); + return assetStore.UploadAsync(fileName, stream, overwrite, ct); } public Task UploadAsync(string tempFile, Stream stream, @@ -77,22 +82,29 @@ namespace Squidex.Domain.Apps.Entities.Assets return assetStore.UploadAsync(tempFile, stream, false, ct); } - public Task CopyAsync(string tempFile, DomainId appId, DomainId id, long fileVersion, + public Task CopyAsync(string tempFile, DomainId appId, DomainId id, long fileVersion, string? suffix, CancellationToken ct = default) { - var fileName = GetFileName(appId, id, fileVersion); + var fileName = GetFileName(appId, id, fileVersion, suffix); return assetStore.CopyAsync(tempFile, fileName, ct); } - public async Task DeleteAsync(DomainId appId, DomainId id, long fileVersion) + public Task DeleteAsync(DomainId appId, DomainId id, long fileVersion, string? suffix) { - var fileNameOld = GetFileName(id, fileVersion); - var fileNameNew = GetFileName(appId, id, fileVersion); + var fileNameOld = GetFileName(id, fileVersion, suffix); + var fileNameNew = GetFileName(appId, id, fileVersion, suffix); - await Task.WhenAll( - assetStore.DeleteAsync(fileNameOld), - assetStore.DeleteAsync(fileNameNew)); + if (options.FolderPerApp) + { + return assetStore.DeleteAsync(fileNameNew); + } + else + { + return Task.WhenAll( + assetStore.DeleteAsync(fileNameOld), + assetStore.DeleteAsync(fileNameNew)); + } } public Task DeleteAsync(string tempFile) @@ -100,14 +112,42 @@ namespace Squidex.Domain.Apps.Entities.Assets return assetStore.DeleteAsync(tempFile); } - private static string GetFileName(DomainId id, long fileVersion) + private static string GetFileName(DomainId id, long fileVersion, string? suffix) { - return $"{id}_{fileVersion}"; + if (!string.IsNullOrWhiteSpace(suffix)) + { + return $"{id}_{fileVersion}_{suffix}"; + } + else + { + return $"{id}_{fileVersion}"; + } } - private static string GetFileName(DomainId appId, DomainId id, long fileVersion) + private string GetFileName(DomainId appId, DomainId id, long fileVersion, string? suffix) { - return $"{appId}_{id}_{fileVersion}"; + if (options.FolderPerApp) + { + if (!string.IsNullOrWhiteSpace(suffix)) + { + return $"derived/{appId}/{id}_{fileVersion}_{suffix}"; + } + else + { + return $"{appId}/{id}_{fileVersion}"; + } + } + else + { + if (!string.IsNullOrWhiteSpace(suffix)) + { + return $"{appId}_{id}_{fileVersion}_{suffix}"; + } + else + { + return $"{appId}_{id}_{fileVersion}"; + } + } } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs index 295488564..edc8985fc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs @@ -34,12 +34,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject IEnumerable assetMetadataSources) : base(grainFactory) { - Guard.NotNull(assetEnricher, nameof(assetEnricher)); - Guard.NotNull(assetFileStore, nameof(assetFileStore)); - Guard.NotNull(assetMetadataSources, nameof(assetMetadataSources)); - Guard.NotNull(assetQuery, nameof(assetQuery)); - Guard.NotNull(contextProvider, nameof(contextProvider)); - this.assetEnricher = assetEnricher; this.assetFileStore = assetFileStore; this.assetMetadataSources = assetMetadataSources.OrderBy(x => x.Order).ToList(); @@ -148,7 +142,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject try { - await assetFileStore.CopyAsync(tempFile, asset.AppId.Id, asset.AssetId, asset.FileVersion); + await assetFileStore.CopyAsync(tempFile, asset.AppId.Id, asset.AssetId, asset.FileVersion, null); } catch (AssetAlreadyExistsException) when (context.Command is not UpsertAsset) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs index cacb45f38..8c8960d5f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -36,10 +36,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject IContentRepository contentRepository) : base(factory, log) { - Guard.NotNull(assetTags, nameof(assetTags)); - Guard.NotNull(assetQuery, nameof(assetQuery)); - Guard.NotNull(contentRepository, nameof(contentRepository)); - this.assetTags = assetTags; this.assetQuery = assetQuery; this.contentRepository = contentRepository; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs index 33f5a53d8..f43143ed1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Assets; -using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -28,8 +27,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject IAssetQueryService assetQuery) : base(factory, log) { - Guard.NotNull(assetQuery, nameof(assetQuery)); - this.assetQuery = assetQuery; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetsBulkUpdateCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetsBulkUpdateCommandMiddleware.cs index 655d9213b..4a15ada0e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetsBulkUpdateCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetsBulkUpdateCommandMiddleware.cs @@ -41,8 +41,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject public AssetsBulkUpdateCommandMiddleware(IContextProvider contextProvider) { - Guard.NotNull(contextProvider, nameof(contextProvider)); - this.contextProvider = contextProvider; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs index d88a815b0..90eaa4185 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs @@ -22,8 +22,6 @@ namespace Squidex.Domain.Apps.Entities.Assets DomainId ParentId { get; } - string MimeType { get; } - bool IsProtected { get; } long FileVersion { get; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetFileStore.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetFileStore.cs index 75dcac29a..a3d1c69ae 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetFileStore.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetFileStore.cs @@ -15,20 +15,20 @@ namespace Squidex.Domain.Apps.Entities.Assets { public interface IAssetFileStore { - string? GeneratePublicUrl(DomainId appId, DomainId id, long fileVersion); + string? GeneratePublicUrl(DomainId appId, DomainId id, long fileVersion, string? suffix); - Task GetFileSizeAsync(DomainId appId, DomainId id, long fileVersion, CancellationToken ct = default); + Task GetFileSizeAsync(DomainId appId, DomainId id, long fileVersion, string? suffix, CancellationToken ct = default); - Task CopyAsync(string tempFile, DomainId appId, DomainId id, long fileVersion, CancellationToken ct = default); + Task CopyAsync(string tempFile, DomainId appId, DomainId id, long fileVersion, string? suffix, CancellationToken ct = default); Task UploadAsync(string tempFile, Stream stream, CancellationToken ct = default); - Task UploadAsync(DomainId appId, DomainId id, long fileVersion, Stream stream, CancellationToken ct = default); + Task UploadAsync(DomainId appId, DomainId id, long fileVersion, string? suffix, Stream stream, bool overwrite = true, CancellationToken ct = default); - Task DownloadAsync(DomainId appId, DomainId id, long fileVersion, Stream stream, BytesRange range = default, CancellationToken ct = default); + Task DownloadAsync(DomainId appId, DomainId id, long fileVersion, string? suffix, Stream stream, BytesRange range = default, CancellationToken ct = default); Task DeleteAsync(string tempFile); - Task DeleteAsync(DomainId appId, DomainId id, long fileVersion); + Task DeleteAsync(DomainId appId, DomainId id, long fileVersion, string? suffix); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs index bece97858..5a4103615 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs @@ -12,7 +12,6 @@ using System.Threading.Tasks; using Squidex.Assets; using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; -using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Assets { @@ -22,8 +21,6 @@ namespace Squidex.Domain.Apps.Entities.Assets public ImageAssetMetadataSource(IAssetThumbnailGenerator assetThumbnailGenerator) { - Guard.NotNull(assetThumbnailGenerator, nameof(assetThumbnailGenerator)); - this.assetThumbnailGenerator = assetThumbnailGenerator; } @@ -96,7 +93,12 @@ namespace Squidex.Domain.Apps.Entities.Assets } } - if (command.Type == AssetType.Image && command.Tags != null) + if (command.Tags == null) + { + return; + } + + if (command.Type == AssetType.Image) { command.Tags.Add("image"); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetEnricher.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetEnricher.cs index e5d0c11d4..58a749372 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetEnricher.cs @@ -26,10 +26,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries public AssetEnricher(ITagService tagService, IEnumerable assetMetadataSources, IRequestCache requestCache) { - Guard.NotNull(tagService, nameof(tagService)); - Guard.NotNull(assetMetadataSources, nameof(assetMetadataSources)); - Guard.NotNull(requestCache, nameof(requestCache)); - this.tagService = tagService; this.assetMetadataSources = assetMetadataSources; this.requestCache = requestCache; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs index b84854b0d..1fada21ec 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs @@ -19,8 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries public AssetLoader(IGrainFactory grainFactory) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - this.grainFactory = grainFactory; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs index 90e3abde6..c5d09dbcb 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs @@ -36,11 +36,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries public AssetQueryParser(IJsonSerializer jsonSerializer, ITagService tagService, IOptions options) { - Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); - Guard.NotNull(options, nameof(options)); - Guard.NotNull(tagService, nameof(tagService)); - this.jsonSerializer = jsonSerializer; + this.tagService = tagService; this.options = options.Value; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs index e82d7ece2..f4ed509e7 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Infrastructure; using Squidex.Log; @@ -22,6 +23,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries private readonly IAssetRepository assetRepository; private readonly IAssetLoader assetLoader; private readonly IAssetFolderRepository assetFolderRepository; + private readonly AssetOptions options; private readonly AssetQueryParser queryParser; public AssetQueryService( @@ -29,18 +31,14 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries IAssetRepository assetRepository, IAssetLoader assetLoader, IAssetFolderRepository assetFolderRepository, + IOptions options, AssetQueryParser queryParser) { - Guard.NotNull(assetEnricher, nameof(assetEnricher)); - Guard.NotNull(assetRepository, nameof(assetRepository)); - Guard.NotNull(assetLoader, nameof(assetLoader)); - Guard.NotNull(assetFolderRepository, nameof(assetFolderRepository)); - Guard.NotNull(queryParser, nameof(queryParser)); - this.assetEnricher = assetEnricher; this.assetRepository = assetRepository; this.assetLoader = assetLoader; this.assetFolderRepository = assetFolderRepository; + this.options = options.Value; this.queryParser = queryParser; } @@ -53,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries while (id != DomainId.Empty) { - var folder = await assetFolderRepository.FindAssetFolderAsync(appId, id, ct); + var folder = await FindFolderCoreAsync(appId, id, ct); if (folder == null || result.Any(x => x.Id == folder.Id)) { @@ -75,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries { using (Profiler.TraceMethod()) { - var assetFolders = await assetFolderRepository.QueryAsync(appId, parentId, ct); + var assetFolders = await QueryFoldersCoreAsync(appId, parentId, ct); return assetFolders; } @@ -86,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries { using (Profiler.TraceMethod()) { - var assetFolders = await assetFolderRepository.QueryAsync(context.App.Id, parentId, ct); + var assetFolders = await QueryFoldersCoreAsync(context, parentId, ct); return assetFolders; } @@ -99,7 +97,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries using (Profiler.TraceMethod()) { - var asset = await assetRepository.FindAssetByHashAsync(context.App.Id, hash, fileName, fileSize, ct); + var asset = await FindByHashCoreAsync(context, hash, fileName, fileSize, ct); if (asset == null) { @@ -117,7 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries using (Profiler.TraceMethod()) { - var asset = await assetRepository.FindAssetBySlugAsync(context.App.Id, slug, ct); + var asset = await FindBySlugCoreAsync(context, slug, ct); if (asset == null) { @@ -135,7 +133,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries using (Profiler.TraceMethod()) { - var asset = await assetRepository.FindAssetAsync(id, ct); + var asset = await FindCoreAsync(id, ct); if (asset == null) { @@ -161,7 +159,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries } else { - asset = await assetRepository.FindAssetAsync(context.App.Id, id, ct); + asset = await FindCoreAsync(context, id, ct); } if (asset == null) @@ -187,7 +185,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries { q = await queryParser.ParseAsync(context, q); - var assets = await assetRepository.QueryAsync(context.App.Id, parentId, q, ct); + var assets = await QueryCoreAsync(context, parentId, q, ct); if (q.Ids != null && q.Ids.Count > 0) { @@ -222,5 +220,100 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries return await assetEnricher.EnrichAsync(assets, context, ct); } } + + private async Task> QueryFoldersCoreAsync(Context context, DomainId parentId, + CancellationToken ct) + { + using (var timeout = new CancellationTokenSource(options.TimeoutQuery)) + { + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, ct)) + { + return await assetFolderRepository.QueryAsync(context.App.Id, parentId, combined.Token); + } + } + } + + private async Task> QueryFoldersCoreAsync(DomainId appId, DomainId parentId, + CancellationToken ct) + { + using (var timeout = new CancellationTokenSource(options.TimeoutQuery)) + { + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, ct)) + { + return await assetFolderRepository.QueryAsync(appId, parentId, combined.Token); + } + } + } + + private async Task> QueryCoreAsync(Context context, DomainId? parentId, Q q, + CancellationToken ct) + { + using (var timeout = new CancellationTokenSource(options.TimeoutQuery)) + { + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, ct)) + { + return await assetRepository.QueryAsync(context.App.Id, parentId, q, combined.Token); + } + } + } + + private async Task FindFolderCoreAsync(DomainId appId, DomainId id, CancellationToken ct) + { + using (var timeout = new CancellationTokenSource(options.TimeoutFind)) + { + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, ct)) + { + return await assetFolderRepository.FindAssetFolderAsync(appId, id, combined.Token); + } + } + } + + private async Task FindByHashCoreAsync(Context context, string hash, string fileName, long fileSize, + CancellationToken ct) + { + using (var timeout = new CancellationTokenSource(options.TimeoutFind)) + { + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, ct)) + { + return await assetRepository.FindAssetByHashAsync(context.App.Id, hash, fileName, fileSize, combined.Token); + } + } + } + + private async Task FindBySlugCoreAsync(Context context, string slug, + CancellationToken ct) + { + using (var timeout = new CancellationTokenSource(options.TimeoutFind)) + { + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, ct)) + { + return await assetRepository.FindAssetBySlugAsync(context.App.Id, slug, combined.Token); + } + } + } + + private async Task FindCoreAsync(DomainId id, + CancellationToken ct) + { + using (var timeout = new CancellationTokenSource(options.TimeoutFind)) + { + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, ct)) + { + return await assetRepository.FindAssetAsync(id, combined.Token); + } + } + } + + private async Task FindCoreAsync(Context context, DomainId id, + CancellationToken ct) + { + using (var timeout = new CancellationTokenSource(options.TimeoutFind)) + { + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, ct)) + { + return await assetRepository.FindAssetAsync(context.App.Id, id, combined.Token); + } + } + } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/RebuildFiles.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/RebuildFiles.cs index 26696979f..1d6aaf181 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/RebuildFiles.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/RebuildFiles.cs @@ -28,10 +28,6 @@ namespace Squidex.Domain.Apps.Entities.Assets IEventStore eventStore, IEventDataFormatter eventDataFormatter) { - Guard.NotNull(assetFileStore, nameof(assetFileStore)); - Guard.NotNull(eventStore, nameof(eventStore)); - Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); - this.assetFileStore = assetFileStore; this.eventStore = eventStore; this.eventDataFormatter = eventDataFormatter; @@ -62,13 +58,13 @@ namespace Squidex.Domain.Apps.Entities.Assets { try { - await assetFileStore.GetFileSizeAsync(appId.Id, id, fileVersion, ct); + await assetFileStore.GetFileSizeAsync(appId.Id, id, fileVersion, null, ct); } catch (AssetNotFoundException) { DummyStream.Position = 0; - await assetFileStore.UploadAsync(appId.Id, id, fileVersion, DummyStream, ct); + await assetFileStore.UploadAsync(appId.Id, id, fileVersion, null, DummyStream, ct: ct); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/RecursiveDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/RecursiveDeleter.cs index 24a7b1eb2..d865d4c0a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/RecursiveDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/RecursiveDeleter.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Events.Assets; -using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -43,11 +42,6 @@ namespace Squidex.Domain.Apps.Entities.Assets TypeNameRegistry typeNameRegistry, ISemanticLog log) { - Guard.NotNull(commandBus, nameof(commandBus)); - Guard.NotNull(assetRepository, nameof(assetRepository)); - Guard.NotNull(assetFolderRepository, nameof(assetFolderRepository)); - Guard.NotNull(log, nameof(log)); - this.commandBus = commandBus; this.assetRepository = assetRepository; this.assetFolderRepository = assetFolderRepository; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/SvgAssetMetadataSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/SvgAssetMetadataSource.cs new file mode 100644 index 000000000..6fad2ff36 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/SvgAssetMetadataSource.cs @@ -0,0 +1,66 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Infrastructure.Translations; +using Squidex.Infrastructure.Validation; +using Squidex.Text; + +namespace Squidex.Domain.Apps.Entities.Assets +{ + public sealed class SvgAssetMetadataSource : IAssetMetadataSource + { + private const int FileSizeLimit = 2 * 1024 * 1024; // 2MB + + public Task EnhanceAsync(UploadAssetCommand command) + { + var isSvg = + command.File.MimeType == "image/svg+xml" || + command.File.FileName.EndsWith(".svg", StringComparison.OrdinalIgnoreCase); + + if (isSvg) + { + command.Tags.Add("image"); + + if (command.File.FileSize < FileSizeLimit) + { + try + { + using (var reader = new StreamReader(command.File.OpenRead())) + { + var text = reader.ReadToEnd(); + + if (!text.IsValidSvg()) + { + throw new ValidationException(T.Get("validation.notAnValidSvg")); + } + } + } + catch (ValidationException) + { + throw; + } + catch + { + return Task.CompletedTask; + } + } + } + + return Task.CompletedTask; + } + + public IEnumerable Format(IAssetEntity asset) + { + yield break; + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs index 96cfa06ab..cc9b5204e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs @@ -54,16 +54,6 @@ namespace Squidex.Domain.Apps.Entities.Backup IUserResolver userResolver, ISemanticLog log) { - Guard.NotNull(backupArchiveLocation, nameof(backupArchiveLocation)); - Guard.NotNull(backupArchiveStore, nameof(backupArchiveStore)); - Guard.NotNull(clock, nameof(clock)); - Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); - Guard.NotNull(eventStore, nameof(eventStore)); - Guard.NotNull(serviceProvider, nameof(serviceProvider)); - Guard.NotNull(state, nameof(state)); - Guard.NotNull(userResolver, nameof(userResolver)); - Guard.NotNull(log, nameof(log)); - this.backupArchiveLocation = backupArchiveLocation; this.backupArchiveStore = backupArchiveStore; this.clock = clock; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupService.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupService.cs index 446df5487..8c3ac5f5b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupService.cs @@ -20,8 +20,6 @@ namespace Squidex.Domain.Apps.Entities.Backup public BackupService(IGrainFactory grainFactory) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - this.grainFactory = grainFactory; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/DefaultBackupArchiveStore.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/DefaultBackupArchiveStore.cs index f6f111971..0bed53d05 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/DefaultBackupArchiveStore.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/DefaultBackupArchiveStore.cs @@ -19,8 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Backup public DefaultBackupArchiveStore(IAssetStore assetStore) { - Guard.NotNull(assetStore, nameof(assetStore)); - this.assetStore = assetStore; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs index 747a6fb7d..7146a16f9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs @@ -61,17 +61,6 @@ namespace Squidex.Domain.Apps.Entities.Backup IUserResolver userResolver, ISemanticLog log) { - Guard.NotNull(backupArchiveLocation, nameof(backupArchiveLocation)); - Guard.NotNull(clock, nameof(clock)); - Guard.NotNull(commandBus, nameof(commandBus)); - Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); - Guard.NotNull(eventStore, nameof(eventStore)); - Guard.NotNull(serviceProvider, nameof(serviceProvider)); - Guard.NotNull(state, nameof(state)); - Guard.NotNull(streamNameResolver, nameof(streamNameResolver)); - Guard.NotNull(userResolver, nameof(userResolver)); - Guard.NotNull(log, nameof(log)); - this.backupArchiveLocation = backupArchiveLocation; this.clock = clock; this.commandBus = commandBus; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/TempFolderBackupArchiveLocation.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/TempFolderBackupArchiveLocation.cs index 10b55341c..0dbe14c36 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/TempFolderBackupArchiveLocation.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/TempFolderBackupArchiveLocation.cs @@ -22,8 +22,6 @@ namespace Squidex.Domain.Apps.Entities.Backup public TempFolderBackupArchiveLocation(IJsonSerializer jsonSerializer) { - Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); - this.jsonSerializer = jsonSerializer; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs index 6e7269c4a..3d0d81f25 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs @@ -15,7 +15,6 @@ using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Comments; -using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; using Squidex.Shared.Users; @@ -31,9 +30,6 @@ namespace Squidex.Domain.Apps.Entities.Comments public CommentTriggerHandler(IScriptEngine scriptEngine, IUserResolver userResolver) { - Guard.NotNull(scriptEngine, nameof(scriptEngine)); - Guard.NotNull(userResolver, nameof(userResolver)); - this.scriptEngine = scriptEngine; this.userResolver = userResolver; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsCommandMiddleware.cs index 77998a5a8..019e161cc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsCommandMiddleware.cs @@ -12,7 +12,6 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Orleans; using Squidex.Domain.Apps.Entities.Comments.Commands; -using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Orleans; using Squidex.Shared.Users; @@ -27,9 +26,6 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject public CommentsCommandMiddleware(IGrainFactory grainFactory, IUserResolver userResolver) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - Guard.NotNull(userResolver, nameof(userResolver)); - this.grainFactory = grainFactory; this.userResolver = userResolver; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsGrain.cs index c98a80613..8931b1812 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsGrain.cs @@ -36,9 +36,6 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject public CommentsGrain(IEventStore eventStore, IEventDataFormatter eventDataFormatter) { - Guard.NotNull(eventStore, nameof(eventStore)); - Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); - this.eventStore = eventStore; this.eventDataFormatter = eventDataFormatter; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/BackupContents.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/BackupContents.cs index e79ad9a0e..705875606 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/BackupContents.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/BackupContents.cs @@ -56,9 +56,6 @@ namespace Squidex.Domain.Apps.Entities.Contents public BackupContents(Rebuilder rebuilder, IUrlGenerator urlGenerator) { - Guard.NotNull(rebuilder, nameof(rebuilder)); - Guard.NotNull(urlGenerator, nameof(urlGenerator)); - this.rebuilder = rebuilder; this.urlGenerator = urlGenerator; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs index 8094f7346..1e9f311e3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs @@ -45,10 +45,6 @@ namespace Squidex.Domain.Apps.Entities.Contents IContentLoader contentLoader, IContentRepository contentRepository) { - Guard.NotNull(scriptEngine, nameof(scriptEngine)); - Guard.NotNull(contentLoader, nameof(contentLoader)); - Guard.NotNull(contentRepository, nameof(contentRepository)); - this.scriptEngine = scriptEngine; this.contentLoader = contentLoader; this.contentRepository = contentRepository; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOptions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOptions.cs index 354d247c9..4abed69cb 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOptions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOptions.cs @@ -5,6 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; + namespace Squidex.Domain.Apps.Entities.Contents { public sealed class ContentOptions @@ -12,5 +14,9 @@ namespace Squidex.Domain.Apps.Entities.Contents public int DefaultPageSize { get; set; } = 200; public int MaxResults { get; set; } = 200; + + public TimeSpan TimeoutFind { get; set; } = TimeSpan.FromSeconds(1); + + public TimeSpan TimeoutQuery { get; set; } = TimeSpan.FromSeconds(5); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerGrain.cs index ef6f42ef0..bb20a5a69 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerGrain.cs @@ -33,11 +33,6 @@ namespace Squidex.Domain.Apps.Entities.Contents IClock clock, ISemanticLog log) { - Guard.NotNull(contentRepository, nameof(contentRepository)); - Guard.NotNull(commandBus, nameof(commandBus)); - Guard.NotNull(clock, nameof(clock)); - Guard.NotNull(log, nameof(log)); - this.clock = clock; this.commandBus = commandBus; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs index 980837024..764a63449 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs @@ -35,11 +35,6 @@ namespace Squidex.Domain.Apps.Entities.Contents ITextIndex contentTextIndexer, IUrlGenerator urlGenerator) { - Guard.NotNull(appProvider, nameof(appProvider)); - Guard.NotNull(contentQuery, nameof(contentQuery)); - Guard.NotNull(contentTextIndexer, nameof(contentTextIndexer)); - Guard.NotNull(urlGenerator, nameof(urlGenerator)); - this.appProvider = appProvider; this.contentQuery = contentQuery; this.contentTextIndexer = contentTextIndexer; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterGrain.cs index a410848b6..71ba48291 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterGrain.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Squidex.Infrastructure; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.States; @@ -25,8 +24,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Counter public CounterGrain(IGrainState state) { - Guard.NotNull(state, nameof(state)); - this.state = state; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterJintExtension.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterJintExtension.cs index 322ea9d8b..203bd7a5e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterJintExtension.cs @@ -19,8 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Counter public CounterJintExtension(IGrainFactory grainFactory) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - this.grainFactory = grainFactory; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs index 2286c4439..a2bc0031a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs @@ -20,8 +20,6 @@ namespace Squidex.Domain.Apps.Entities.Contents public DefaultWorkflowsValidator(IAppProvider appProvider) { - Guard.NotNull(appProvider, nameof(appProvider)); - this.appProvider = appProvider; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentCommandMiddleware.cs index bcb6c14fe..7458c39e8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentCommandMiddleware.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Orleans; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Queries; -using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Contents.DomainObject @@ -22,9 +21,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject public ContentCommandMiddleware(IGrainFactory grainFactory, IContentEnricher contentEnricher, IContextProvider contextProvider) : base(grainFactory) { - Guard.NotNull(contentEnricher, nameof(contentEnricher)); - Guard.NotNull(contextProvider, nameof(contextProvider)); - this.contentEnricher = contentEnricher; this.contextProvider = contextProvider; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs index e4fa44528..d80aff0e9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs @@ -33,8 +33,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject IServiceProvider serviceProvider) : base(persistence, log) { - Guard.NotNull(serviceProvider, nameof(serviceProvider)); - this.serviceProvider = serviceProvider; Capacity = int.MaxValue; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentsBulkUpdateCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentsBulkUpdateCommandMiddleware.cs index 9b2575f26..8ac0a09d2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentsBulkUpdateCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentsBulkUpdateCommandMiddleware.cs @@ -47,9 +47,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject public ContentsBulkUpdateCommandMiddleware(IContentQueryService contentQuery, IContextProvider contextProvider) { - Guard.NotNull(contentQuery, nameof(contentQuery)); - Guard.NotNull(contextProvider, nameof(contextProvider)); - this.contentQuery = contentQuery; this.contextProvider = contextProvider; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs index 88f8c8739..3ccc8dcaa 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs @@ -23,9 +23,6 @@ namespace Squidex.Domain.Apps.Entities.Contents public DynamicContentWorkflow(IScriptEngine scriptEngine, IAppProvider appProvider) { - Guard.NotNull(scriptEngine, nameof(scriptEngine)); - Guard.NotNull(appProvider, nameof(appProvider)); - this.scriptEngine = scriptEngine; this.appProvider = appProvider; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs index 28242adad..463330f73 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs @@ -40,11 +40,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL public CachingGraphQLService(IBackgroundCache cache, ISchemasHash schemasHash, IServiceProvider serviceProvider, IOptions options) { - Guard.NotNull(cache, nameof(cache)); - Guard.NotNull(schemasHash, nameof(schemasHash)); - Guard.NotNull(serviceProvider, nameof(serviceProvider)); - Guard.NotNull(options, nameof(options)); - this.cache = cache; this.schemasHash = schemasHash; this.serviceProvider = serviceProvider; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/DefaultDocumentWriter.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/DefaultDocumentWriter.cs index 90a5d5130..9b15fcd32 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/DefaultDocumentWriter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/DefaultDocumentWriter.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using GraphQL; using Microsoft.AspNetCore.WebUtilities; -using Squidex.Infrastructure; using Squidex.Infrastructure.Json; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL @@ -21,8 +20,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL public DefaultDocumentWriter(IJsonSerializer jsonSerializer) { - Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); - this.jsonSerializer = jsonSerializer; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs index d24bf1894..67e605fed 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs @@ -29,9 +29,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries public ContentEnricher(IEnumerable steps, Lazy contentQuery) { - Guard.NotNull(steps, nameof(steps)); - Guard.NotNull(contentQuery, nameof(contentQuery)); - this.steps = steps; this.contentQuery = contentQuery; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs index 0bdbb3f8a..def19558c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs @@ -19,8 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries public ContentLoader(IGrainFactory grainFactory) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - this.grainFactory = grainFactory; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs index 4befe088e..c56a577f3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs @@ -45,11 +45,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries public ContentQueryParser(IMemoryCache cache, IJsonSerializer jsonSerializer, ITextIndex textIndex, IOptions options) { - Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); - Guard.NotNull(textIndex, nameof(textIndex)); - Guard.NotNull(cache, nameof(cache)); - Guard.NotNull(options, nameof(options)); - this.jsonSerializer = jsonSerializer; this.textIndex = textIndex; this.cache = cache; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs index 2aa1dd54f..c430b46c5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; @@ -17,6 +18,7 @@ using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Translations; using Squidex.Log; using Squidex.Shared; +using Squidex.Shared.Identity; namespace Squidex.Domain.Apps.Entities.Contents.Queries { @@ -28,25 +30,21 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries private readonly IContentRepository contentRepository; private readonly IContentLoader contentLoader; private readonly ContentQueryParser queryParser; + private readonly ContentOptions options; public ContentQueryService( IAppProvider appProvider, IContentEnricher contentEnricher, IContentRepository contentRepository, IContentLoader contentLoader, + IOptions options, ContentQueryParser queryParser) { - Guard.NotNull(appProvider, nameof(appProvider)); - Guard.NotNull(contentEnricher, nameof(contentEnricher)); - Guard.NotNull(contentRepository, nameof(contentRepository)); - Guard.NotNull(contentLoader, nameof(contentLoader)); - Guard.NotNull(queryParser, nameof(queryParser)); - this.appProvider = appProvider; this.contentEnricher = contentEnricher; this.contentRepository = contentRepository; this.contentLoader = contentLoader; - this.queryParser = queryParser; + this.options = options.Value; this.queryParser = queryParser; } @@ -67,7 +65,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries } else { - content = await contentRepository.FindContentAsync(context.App, schema, id, context.Scope(), ct); + content = await FindCoreAsync(context, id, schema, ct); } if (content == null || content.SchemaId.Id != schema.Id) @@ -100,7 +98,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries q = await queryParser.ParseAsync(context, q, schema); - var contents = await contentRepository.QueryAsync(context.App, schema, q, context.Scope(), ct); + var contents = await QueryCoreAsync(context, q, schema, ct); if (q.Ids != null && q.Ids.Count > 0) { @@ -132,7 +130,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries q = await queryParser.ParseAsync(context, q); - var contents = await contentRepository.QueryAsync(context.App, schemas, q, context.Scope(), ct); + var contents = await QueryCoreAsync(context, q, schemas, ct); if (q.Ids != null && q.Ids.Count > 0) { @@ -216,6 +214,42 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries return schemas.Where(x => IsAccessible(x) && HasPermission(context, x, Permissions.AppContentsReadOwn)).ToList(); } + private async Task> QueryCoreAsync(Context context, Q q, ISchemaEntity schema, + CancellationToken ct) + { + using (var timeout = new CancellationTokenSource(options.TimeoutQuery)) + { + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, ct)) + { + return await contentRepository.QueryAsync(context.App, schema, q, context.Scope(), ct); + } + } + } + + private async Task> QueryCoreAsync(Context context, Q q, List schemas, + CancellationToken ct) + { + using (var timeout = new CancellationTokenSource(options.TimeoutQuery)) + { + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, ct)) + { + return await contentRepository.QueryAsync(context.App, schemas, q, context.Scope(), ct); + } + } + } + + private async Task FindCoreAsync(Context context, DomainId id, ISchemaEntity schema, + CancellationToken ct) + { + using (var timeout = new CancellationTokenSource(options.TimeoutFind)) + { + using (var combined = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, ct)) + { + return await contentRepository.FindContentAsync(context.App, schema, id, context.Scope(), combined.Token); + } + } + } + private static bool IsAccessible(ISchemaEntity schema) { return schema.SchemaDef.IsPublished; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs index 655811602..b889aac8d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs @@ -34,11 +34,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps public ConvertData(IUrlGenerator urlGenerator, IJsonSerializer jsonSerializer, IAssetRepository assetRepository, IContentRepository contentRepository) { - Guard.NotNull(urlGenerator, nameof(urlGenerator)); - Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); - Guard.NotNull(assetRepository, nameof(assetRepository)); - Guard.NotNull(contentRepository, nameof(contentRepository)); - this.urlGenerator = urlGenerator; this.assetRepository = assetRepository; this.contentRepository = contentRepository; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichForCaching.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichForCaching.cs index 949806aef..1dc602d6d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichForCaching.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichForCaching.cs @@ -9,7 +9,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps @@ -20,8 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps public EnrichForCaching(IRequestCache requestCache) { - Guard.NotNull(requestCache, nameof(requestCache)); - this.requestCache = requestCache; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithWorkflows.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithWorkflows.cs index 4dda4332b..47b23dbf5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithWorkflows.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithWorkflows.cs @@ -22,8 +22,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps public EnrichWithWorkflows(IContentWorkflow contentWorkflow) { - Guard.NotNull(contentWorkflow, nameof(contentWorkflow)); - this.contentWorkflow = contentWorkflow; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs index f3a47f937..7b9bd142f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs @@ -32,10 +32,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps public ResolveAssets(IUrlGenerator urlGenerator, IAssetQueryService assetQuery, IRequestCache requestCache) { - Guard.NotNull(urlGenerator, nameof(urlGenerator)); - Guard.NotNull(assetQuery, nameof(assetQuery)); - Guard.NotNull(requestCache, nameof(requestCache)); - this.urlGenerator = urlGenerator; this.assetQuery = assetQuery; this.requestCache = requestCache; @@ -90,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps { IJsonValue array; - if (referencedAsset.Type == AssetType.Image) + if (IsImage(referencedAsset)) { var url = urlGenerator.AssetContent( referencedAsset.AppId, @@ -113,6 +109,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps } } + private static bool IsImage(IEnrichedAssetEntity asset) + { + const int PreviewLimit = 10 * 1024; + + return asset.Type == AssetType.Image || (asset.MimeType == "image/svg+xml" && asset.FileSize < PreviewLimit); + } + private async Task> GetAssetsAsync(Context context, HashSet ids, CancellationToken ct) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs index fcaa8afd2..cbae70937 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs @@ -34,9 +34,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps public ResolveReferences(Lazy contentQuery, IRequestCache requestCache) { - Guard.NotNull(contentQuery, nameof(contentQuery)); - Guard.NotNull(requestCache, nameof(requestCache)); - this.contentQuery = contentQuery; this.requestCache = requestCache; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs index f0cf237f1..b17db069b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Scripting; -using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps { @@ -20,8 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps public ScriptContent(IScriptEngine scriptEngine) { - Guard.NotNull(scriptEngine, nameof(scriptEngine)); - this.scriptEngine = scriptEngine; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesFluidExtension.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesFluidExtension.cs index 67a427a6c..95df274e2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesFluidExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesFluidExtension.cs @@ -13,11 +13,10 @@ using System.Text.Encodings.Web; using System.Threading.Tasks; using Fluid; using Fluid.Ast; -using Fluid.Tags; +using Fluid.Values; using Microsoft.Extensions.DependencyInjection; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Templates; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure; #pragma warning disable CA1826 // Do not use Enumerable methods on indexable collections @@ -26,43 +25,26 @@ namespace Squidex.Domain.Apps.Entities.Contents { public sealed class ReferencesFluidExtension : IFluidExtension { + private static readonly FluidValue ErrorNullReference = FluidValue.Create(null); private readonly IServiceProvider serviceProvider; - private sealed class ReferenceTag : ArgumentsTag + private sealed class ReferenceTag : AppTag { - private readonly IServiceProvider serviceProvider; + private readonly IContentQueryService contentQuery; public ReferenceTag(IServiceProvider serviceProvider) + : base(serviceProvider) { - this.serviceProvider = serviceProvider; + contentQuery = serviceProvider.GetRequiredService(); } public override async ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context, FilterArgument[] arguments) { if (arguments.Length == 2 && context.GetValue("event")?.ToObjectValue() is EnrichedEvent enrichedEvent) { - var app = await GetAppAsync(enrichedEvent); + var id = await arguments[1].Expression.EvaluateAsync(context); - if (app == null) - { - return Completion.Normal; - } - - var requestContext = - Context.Admin(app).Clone(b => b - .WithoutContentEnrichment() - .WithUnpublished() - .WithoutTotal()); - - var id = (await arguments[1].Expression.EvaluateAsync(context)).ToStringValue(); - - var domainId = DomainId.Create(id); - var domainIds = new List { domainId }; - - var contentQuery = serviceProvider.GetRequiredService(); - - var contents = await contentQuery.QueryAsync(requestContext, Q.Empty.WithIds(domainIds)); - var content = contents.FirstOrDefault(); + var content = await ResolveContentAsync(AppProvider, contentQuery, enrichedEvent.AppId.Id, id); if (content != null) { @@ -74,19 +56,10 @@ namespace Squidex.Domain.Apps.Entities.Contents return Completion.Normal; } - - private Task GetAppAsync(EnrichedEvent enrichedEvent) - { - var appProvider = serviceProvider.GetRequiredService(); - - return appProvider.GetAppAsync(enrichedEvent.AppId.Id, false); - } } public ReferencesFluidExtension(IServiceProvider serviceProvider) { - Guard.NotNull(serviceProvider, nameof(serviceProvider)); - this.serviceProvider = serviceProvider; } @@ -98,11 +71,60 @@ namespace Squidex.Domain.Apps.Entities.Contents memberAccessStrategy.Register(); memberAccessStrategy.Register(); memberAccessStrategy.Register(); + + AddReferenceFilter(); + } + + private void AddReferenceFilter() + { + var appProvider = serviceProvider.GetRequiredService(); + + var contentQuery = serviceProvider.GetRequiredService(); + + TemplateContext.GlobalFilters.AddAsyncFilter("reference", async (input, arguments, context) => + { + if (context.GetValue("event")?.ToObjectValue() is EnrichedEvent enrichedEvent) + { + var content = await ResolveContentAsync(appProvider, contentQuery, enrichedEvent.AppId.Id, input); + + if (content == null) + { + return ErrorNullReference; + } + + return FluidValue.Create(content); + } + + return ErrorNullReference; + }); } public void RegisterLanguageExtensions(FluidParserFactory factory) { factory.RegisterTag("reference", new ReferenceTag(serviceProvider)); } + + private static async Task ResolveContentAsync(IAppProvider appProvider, IContentQueryService contentQuery, DomainId appId, FluidValue id) + { + var app = await appProvider.GetAppAsync(appId); + + if (app == null) + { + return null; + } + + var domainId = DomainId.Create(id.ToStringValue()); + var domainIds = new List { domainId }; + + var requestContext = + Context.Admin(app).Clone(b => b + .WithUnpublished() + .WithoutTotal()); + + var contents = await contentQuery.QueryAsync(requestContext, Q.Empty.WithIds(domainIds)); + var content = contents.FirstOrDefault(); + + return content; + } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs index 5079373bf..5562a147a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs @@ -27,8 +27,6 @@ namespace Squidex.Domain.Apps.Entities.Contents public ReferencesJintExtension(IServiceProvider serviceProvider) { - Guard.NotNull(serviceProvider, nameof(serviceProvider)); - this.serviceProvider = serviceProvider; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Elastic/ElasticSearchTextIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Elastic/ElasticSearchTextIndex.cs index ee057d813..05a9ac38b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Elastic/ElasticSearchTextIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Elastic/ElasticSearchTextIndex.cs @@ -27,9 +27,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text.Elastic public ElasticSearchTextIndex(string configurationString, string indexName, bool waitForTesting = false) { - Guard.NotNull(configurationString, nameof(configurationString)); - Guard.NotNull(indexName, nameof(indexName)); - var config = new ConnectionConfiguration(new Uri(configurationString)); client = new ElasticLowLevelClient(config); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexingProcess.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexingProcess.cs index f5c358dbe..38a5822a7 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexingProcess.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexingProcess.cs @@ -335,10 +335,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text ITextIndex textIndex, ITextIndexerState textIndexerState) { - Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); - Guard.NotNull(textIndex, nameof(textIndex)); - Guard.NotNull(textIndexerState, nameof(textIndexerState)); - this.jsonSerializer = jsonSerializer; this.textIndex = textIndex; this.textIndexerState = textIndexerState; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Validation/DependencyValidatorsFactory.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Validation/DependencyValidatorsFactory.cs index bedba96db..2a88bafc8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Validation/DependencyValidatorsFactory.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Validation/DependencyValidatorsFactory.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent.Validators; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories; -using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.Validation { @@ -22,9 +21,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Validation public DependencyValidatorsFactory(IAssetRepository assetRepository, IContentRepository contentRepository) { - Guard.NotNull(assetRepository, nameof(assetRepository)); - Guard.NotNull(contentRepository, nameof(contentRepository)); - this.assetRepository = assetRepository; this.contentRepository = contentRepository; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/History/HistoryService.cs b/backend/src/Squidex.Domain.Apps.Entities/History/HistoryService.cs index b2b3cc4aa..a746ef0a9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/History/HistoryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/History/HistoryService.cs @@ -39,10 +39,6 @@ namespace Squidex.Domain.Apps.Entities.History public HistoryService(IHistoryEventRepository repository, IEnumerable creators, NotifoService notifo) { - Guard.NotNull(repository, nameof(repository)); - Guard.NotNull(creators, nameof(creators)); - Guard.NotNull(notifo, nameof(notifo)); - this.creators = creators.ToList(); foreach (var creator in this.creators) diff --git a/backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs b/backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs index 0ce37bee2..9c2ba4313 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs @@ -42,17 +42,12 @@ namespace Squidex.Domain.Apps.Entities.History ISemanticLog log, IClock clock) { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(urlGenerator, nameof(urlGenerator)); - Guard.NotNull(userResolver, nameof(userResolver)); - Guard.NotNull(log, nameof(log)); - Guard.NotNull(clock, nameof(clock)); - this.options = options.Value; this.urlGenerator = urlGenerator; this.userResolver = userResolver; this.clock = clock; + this.log = log; if (options.Value.IsConfigured()) @@ -72,10 +67,10 @@ namespace Squidex.Domain.Apps.Entities.History public async Task OnUserCreatedAsync(IUser user) { - if (!string.IsNullOrWhiteSpace(user.Email)) - { - await UpsertUserAsync(user); - } + if (!string.IsNullOrWhiteSpace(user.Email)) + { + await UpsertUserAsync(user); + } } public async Task OnUserUpdatedAsync(IUser user, IUser previous) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Notifications/NotificationEmailSender.cs b/backend/src/Squidex.Domain.Apps.Entities/Notifications/NotificationEmailSender.cs index 8782554a2..626e53ea0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Notifications/NotificationEmailSender.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Notifications/NotificationEmailSender.cs @@ -50,14 +50,10 @@ namespace Squidex.Domain.Apps.Entities.Notifications IUrlGenerator urlGenerator, ISemanticLog log) { - Guard.NotNull(texts, nameof(texts)); - Guard.NotNull(emailSender, nameof(emailSender)); - Guard.NotNull(urlGenerator, nameof(urlGenerator)); - Guard.NotNull(log, nameof(log)); - this.texts = texts.Value; this.emailSender = emailSender; this.urlGenerator = urlGenerator; + this.log = log; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/BackupRules.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/BackupRules.cs index bff44e872..da9d4a37d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/BackupRules.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/BackupRules.cs @@ -24,8 +24,6 @@ namespace Squidex.Domain.Apps.Entities.Rules public BackupRules(IRulesIndex indexForRules) { - Guard.NotNull(indexForRules, nameof(indexForRules)); - this.indexForRules = indexForRules; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.cs index a081c499e..bf5555e87 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.DomainObject.Guards; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Rules; -using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -29,9 +28,6 @@ namespace Squidex.Domain.Apps.Entities.Rules.DomainObject IAppProvider appProvider, IRuleEnqueuer ruleEnqueuer) : base(factory, log) { - Guard.NotNull(appProvider, nameof(appProvider)); - Guard.NotNull(ruleEnqueuer, nameof(ruleEnqueuer)); - this.appProvider = appProvider; this.ruleEnqueuer = ruleEnqueuer; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs index 1d07650c9..737322d39 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs @@ -23,8 +23,6 @@ namespace Squidex.Domain.Apps.Entities.Rules.Indexes public RulesIndex(IGrainFactory grainFactory) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - this.grainFactory = grainFactory; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleEnricher.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleEnricher.cs index f7d5d1b36..b4b1b2598 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleEnricher.cs @@ -23,9 +23,6 @@ namespace Squidex.Domain.Apps.Entities.Rules.Queries public RuleEnricher(IRuleEventRepository ruleEventRepository, IRequestCache requestCache) { - Guard.NotNull(ruleEventRepository, nameof(ruleEventRepository)); - Guard.NotNull(requestCache, nameof(requestCache)); - this.ruleEventRepository = ruleEventRepository; this.requestCache = requestCache; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleQueryService.cs index c73d6bfda..91d63dfd9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleQueryService.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using Squidex.Domain.Apps.Entities.Rules.Indexes; -using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Rules.Queries { @@ -20,9 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Rules.Queries public RuleQueryService(IRulesIndex rulesIndex, IRuleEnricher ruleEnricher) { - Guard.NotNull(rulesIndex, nameof(rulesIndex)); - Guard.NotNull(ruleEnricher, nameof(ruleEnricher)); - this.rulesIndex = rulesIndex; this.ruleEnricher = ruleEnricher; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs index d125dd863..624363c14 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Orleans; using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.DomainObject; -using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Rules @@ -22,9 +21,6 @@ namespace Squidex.Domain.Apps.Entities.Rules public RuleCommandMiddleware(IGrainFactory grainFactory, IRuleEnricher ruleEnricher, IContextProvider contextProvider) : base(grainFactory) { - Guard.NotNull(ruleEnricher, nameof(ruleEnricher)); - Guard.NotNull(contextProvider, nameof(contextProvider)); - this.ruleEnricher = ruleEnricher; this.contextProvider = contextProvider; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuerGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuerGrain.cs index ddfcdc24f..eba840650 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuerGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuerGrain.cs @@ -35,11 +35,6 @@ namespace Squidex.Domain.Apps.Entities.Rules IRuleEventRepository ruleEventRepository, ISemanticLog log, IClock clock) { - Guard.NotNull(ruleEventRepository, nameof(ruleEventRepository)); - Guard.NotNull(ruleService, nameof(ruleService)); - Guard.NotNull(clock, nameof(clock)); - Guard.NotNull(log, nameof(log)); - this.ruleEventRepository = ruleEventRepository; this.ruleService = ruleService; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs index 76d8f6610..48669a9e8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs @@ -37,12 +37,6 @@ namespace Squidex.Domain.Apps.Entities.Rules IRuleEventRepository ruleEventRepository, IRuleService ruleService) { - Guard.NotNull(appProvider, nameof(appProvider)); - Guard.NotNull(cache, nameof(cache)); - Guard.NotNull(localCache, nameof(localCache)); - Guard.NotNull(ruleEventRepository, nameof(ruleEventRepository)); - Guard.NotNull(ruleService, nameof(ruleService)); - this.appProvider = appProvider; this.cache = cache; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs index 84ae5d952..050415da9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs @@ -31,11 +31,6 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner IEventDataFormatter eventDataFormatter, IRuleService ruleService) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); - Guard.NotNull(eventStore, nameof(eventStore)); - Guard.NotNull(ruleService, nameof(ruleService)); - this.grainFactory = grainFactory; this.eventDataFormatter = eventDataFormatter; this.eventStore = eventStore; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs index 507c854bc..5d3597fad 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs @@ -60,15 +60,6 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner IRuleService ruleService, ISemanticLog log) { - Guard.NotNull(state, nameof(state)); - Guard.NotNull(appProvider, nameof(appProvider)); - Guard.NotNull(localCache, nameof(localCache)); - Guard.NotNull(eventStore, nameof(eventStore)); - Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); - Guard.NotNull(ruleEventRepository, nameof(ruleEventRepository)); - Guard.NotNull(ruleService, nameof(ruleService)); - Guard.NotNull(log, nameof(log)); - this.state = state; this.appProvider = appProvider; this.localCache = localCache; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerCommandMiddleware.cs index 24d75a853..8196a5c1e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerCommandMiddleware.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Orleans; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Entities.Rules.Commands; -using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Orleans; @@ -21,8 +20,6 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking public UsageTrackerCommandMiddleware(IGrainFactory grainFactory) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - this.grainFactory = grainFactory; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs index 19de1d9e9..4081418a3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs @@ -66,9 +66,6 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking public UsageTrackerGrain(IGrainState state, IApiUsageTracker usageTracker) { - Guard.NotNull(state, nameof(state)); - Guard.NotNull(usageTracker, nameof(usageTracker)); - this.state = state; this.usageTracker = usageTracker; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/BackupSchemas.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/BackupSchemas.cs index 572246578..4f69a8d29 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/BackupSchemas.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/BackupSchemas.cs @@ -24,8 +24,6 @@ namespace Squidex.Domain.Apps.Entities.Schemas public BackupSchemas(ISchemasIndex indexSchemas) { - Guard.NotNull(indexSchemas, nameof(indexSchemas)); - this.indexSchemas = indexSchemas; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/ISchemasHash.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/ISchemasHash.cs index 659c01d09..3ddeef9f1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/ISchemasHash.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/ISchemasHash.cs @@ -9,7 +9,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using NodaTime; using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Schemas { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs index f6c7b486b..f7d86dedb 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs @@ -30,9 +30,6 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes public SchemasIndex(IGrainFactory grainFactory, IReplicatedCache grainCache) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - Guard.NotNull(grainCache, nameof(grainCache)); - this.grainFactory = grainFactory; this.grainCache = grainCache; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs index 9c447addd..64d177d2a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs @@ -16,7 +16,6 @@ using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Schemas; -using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -30,8 +29,6 @@ namespace Squidex.Domain.Apps.Entities.Schemas public SchemaChangedTriggerHandler(IScriptEngine scriptEngine) { - Guard.NotNull(scriptEngine, nameof(scriptEngine)); - this.scriptEngine = scriptEngine; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs index 1540fe36e..855e4d8af 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs @@ -24,9 +24,6 @@ namespace Squidex.Domain.Apps.Entities.Schemas public SchemasSearchSource(IAppProvider appProvider, IUrlGenerator urlGenerator) { - Guard.NotNull(appProvider, nameof(appProvider)); - Guard.NotNull(urlGenerator, nameof(urlGenerator)); - this.appProvider = appProvider; this.urlGenerator = urlGenerator; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Search/SearchManager.cs b/backend/src/Squidex.Domain.Apps.Entities/Search/SearchManager.cs index c8fbe8fcd..1995bd2f0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Search/SearchManager.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Search/SearchManager.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Squidex.Infrastructure; using Squidex.Log; namespace Squidex.Domain.Apps.Entities.Search @@ -23,9 +22,6 @@ namespace Squidex.Domain.Apps.Entities.Search public SearchManager(IEnumerable searchSources, ISemanticLog log) { - Guard.NotNull(searchSources, nameof(searchSources)); - Guard.NotNull(log, nameof(log)); - this.searchSources = searchSources; this.log = log; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj index 9a863a553..14f8445e0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj +++ b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj @@ -17,21 +17,21 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + all runtime; build; native; contentfiles; analyzers - + diff --git a/backend/src/Squidex.Domain.Apps.Entities/Tags/GrainTagService.cs b/backend/src/Squidex.Domain.Apps.Entities/Tags/GrainTagService.cs index 9964a2a55..53629c5b4 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Tags/GrainTagService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Tags/GrainTagService.cs @@ -19,8 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Tags public GrainTagService(IGrainFactory grainFactory) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - this.grainFactory = grainFactory; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Tags/TagGrain.cs b/backend/src/Squidex.Domain.Apps.Entities/Tags/TagGrain.cs index f6f2d8860..41249e4c5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Tags/TagGrain.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Tags/TagGrain.cs @@ -28,8 +28,6 @@ namespace Squidex.Domain.Apps.Entities.Tags public TagGrain(IGrainState state) { - Guard.NotNull(state, nameof(state)); - this.state = state; } diff --git a/backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj b/backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj index 7027871a3..bfd397200 100644 --- a/backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj +++ b/backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj @@ -19,7 +19,7 @@ - + diff --git a/backend/src/Squidex.Domain.Users/DefaultKeyStore.cs b/backend/src/Squidex.Domain.Users/DefaultKeyStore.cs index 5a7e305fe..eb4164005 100644 --- a/backend/src/Squidex.Domain.Users/DefaultKeyStore.cs +++ b/backend/src/Squidex.Domain.Users/DefaultKeyStore.cs @@ -12,7 +12,6 @@ using IdentityModel; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using OpenIddict.Server; -using Squidex.Infrastructure; using Squidex.Infrastructure.States; namespace Squidex.Domain.Users @@ -31,8 +30,6 @@ namespace Squidex.Domain.Users public DefaultKeyStore(ISnapshotStore store) { - Guard.NotNull(store, nameof(store)); - this.store = store; } diff --git a/backend/src/Squidex.Domain.Users/DefaultUserPictureStore.cs b/backend/src/Squidex.Domain.Users/DefaultUserPictureStore.cs index da8edb806..51d7648ea 100644 --- a/backend/src/Squidex.Domain.Users/DefaultUserPictureStore.cs +++ b/backend/src/Squidex.Domain.Users/DefaultUserPictureStore.cs @@ -9,7 +9,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using Squidex.Assets; -using Squidex.Infrastructure; namespace Squidex.Domain.Users { @@ -19,8 +18,6 @@ namespace Squidex.Domain.Users public DefaultUserPictureStore(IAssetStore assetStore) { - Guard.NotNull(assetStore, nameof(assetStore)); - this.assetStore = assetStore; } diff --git a/backend/src/Squidex.Domain.Users/DefaultUserResolver.cs b/backend/src/Squidex.Domain.Users/DefaultUserResolver.cs index 390257e9d..2b571ddcb 100644 --- a/backend/src/Squidex.Domain.Users/DefaultUserResolver.cs +++ b/backend/src/Squidex.Domain.Users/DefaultUserResolver.cs @@ -24,8 +24,6 @@ namespace Squidex.Domain.Users public DefaultUserResolver(IServiceProvider serviceProvider) { - Guard.NotNull(serviceProvider, nameof(serviceProvider)); - this.serviceProvider = serviceProvider; } diff --git a/backend/src/Squidex.Domain.Users/DefaultUserService.cs b/backend/src/Squidex.Domain.Users/DefaultUserService.cs index 75d69d259..6b0e23422 100644 --- a/backend/src/Squidex.Domain.Users/DefaultUserService.cs +++ b/backend/src/Squidex.Domain.Users/DefaultUserService.cs @@ -30,11 +30,6 @@ namespace Squidex.Domain.Users public DefaultUserService(UserManager userManager, IUserFactory userFactory, IEnumerable userEvents, ISemanticLog log) { - Guard.NotNull(userManager, nameof(userManager)); - Guard.NotNull(userFactory, nameof(userFactory)); - Guard.NotNull(userEvents, nameof(userEvents)); - Guard.NotNull(log, nameof(log)); - this.userManager = userManager; this.userFactory = userFactory; this.userEvents = userEvents; @@ -93,7 +88,7 @@ namespace Squidex.Domain.Users return result; } - var userItems = QueryUsers(query).Take(take).Skip(skip).ToList(); + var userItems = QueryUsers(query).Skip(skip).Take(take).ToList(); var userTotal = QueryUsers(query).LongCount(); var resolved = await ResolveAsync(userItems); diff --git a/backend/src/Squidex.Domain.Users/DefaultXmlRepository.cs b/backend/src/Squidex.Domain.Users/DefaultXmlRepository.cs index dbb0ccaff..cdca50443 100644 --- a/backend/src/Squidex.Domain.Users/DefaultXmlRepository.cs +++ b/backend/src/Squidex.Domain.Users/DefaultXmlRepository.cs @@ -40,8 +40,6 @@ namespace Squidex.Domain.Users public DefaultXmlRepository(ISnapshotStore store) { - Guard.NotNull(store, nameof(store)); - this.store = store; } diff --git a/backend/src/Squidex.Domain.Users/PwnedPasswordValidator.cs b/backend/src/Squidex.Domain.Users/PwnedPasswordValidator.cs deleted file mode 100644 index 8ac7d4d93..000000000 --- a/backend/src/Squidex.Domain.Users/PwnedPasswordValidator.cs +++ /dev/null @@ -1,58 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using SharpPwned.NET; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Translations; -using Squidex.Log; - -namespace Squidex.Domain.Users -{ - public sealed class PwnedPasswordValidator : IPasswordValidator - { - private readonly HaveIBeenPwnedRestClient client = new HaveIBeenPwnedRestClient(); - private readonly ISemanticLog log; - - public PwnedPasswordValidator(ISemanticLog log) - { - Guard.NotNull(log, nameof(log)); - - this.log = log; - } - - public async Task ValidateAsync(UserManager manager, IdentityUser user, string password) - { - if (string.IsNullOrWhiteSpace(password)) - { - return IdentityResult.Success; - } - - try - { - var isBreached = await client.IsPasswordPwned(password); - - if (isBreached) - { - var errorText = T.Get("security.passwordStolen"); - - return IdentityResult.Failed(new IdentityError { Code = "PwnedError", Description = errorText }); - } - } - catch (Exception ex) - { - log.LogError(ex, w => w - .WriteProperty("operation", "CheckPasswordPwned") - .WriteProperty("status", "Failed")); - } - - return IdentityResult.Success; - } - } -} diff --git a/backend/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj b/backend/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj index 173b054b3..976c9b9f9 100644 --- a/backend/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj +++ b/backend/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj @@ -17,11 +17,11 @@ - + - + - + diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs index 95c8fc632..704260f98 100644 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs +++ b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs @@ -36,11 +36,6 @@ namespace Squidex.Infrastructure.EventSourcing public CosmosDbEventStore(DocumentClient documentClient, string masterKey, string database, IJsonSerializer jsonSerializer) { - Guard.NotNull(documentClient, nameof(documentClient)); - Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); - Guard.NotNullOrEmpty(masterKey, nameof(masterKey)); - Guard.NotNullOrEmpty(database, nameof(database)); - this.documentClient = documentClient; databaseUri = UriFactory.CreateDatabaseUri(database); diff --git a/backend/src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs b/backend/src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs index 362972559..4636480f4 100644 --- a/backend/src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs +++ b/backend/src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs @@ -18,8 +18,6 @@ namespace Squidex.Infrastructure.Diagnostics public GetEventStoreHealthCheck(IEventStoreConnection connection) { - Guard.NotNull(connection, nameof(connection)); - this.connection = connection; } diff --git a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs b/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs index 929a2a23e..25c26a8a8 100644 --- a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs +++ b/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs @@ -32,9 +32,6 @@ namespace Squidex.Infrastructure.EventSourcing public GetEventStore(IEventStoreConnection connection, IJsonSerializer serializer, string prefix, string projectionHost) { - Guard.NotNull(connection, nameof(connection)); - Guard.NotNull(serializer, nameof(serializer)); - this.connection = connection; this.serializer = serializer; diff --git a/backend/src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoDBHealthCheck.cs b/backend/src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoDBHealthCheck.cs index 3ca573dda..971b08bb1 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoDBHealthCheck.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoDBHealthCheck.cs @@ -18,8 +18,6 @@ namespace Squidex.Infrastructure.Diagnostics public MongoDBHealthCheck(IMongoDatabase mongoDatabase) { - Guard.NotNull(mongoDatabase, nameof(mongoDatabase)); - this.mongoDatabase = mongoDatabase; } diff --git a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs b/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs index aa0ad9aa4..974fb710e 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs @@ -38,8 +38,6 @@ namespace Squidex.Infrastructure.EventSourcing public MongoEventStore(IMongoDatabase database, IEventNotifier notifier) : base(database) { - Guard.NotNull(notifier, nameof(notifier)); - this.notifier = notifier; } diff --git a/backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj b/backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj index 3e502c1b4..ba8b59a5a 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj +++ b/backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/backend/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageRepository.cs b/backend/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageRepository.cs index 26b41a69b..4837cf42f 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageRepository.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageRepository.cs @@ -55,6 +55,8 @@ namespace Squidex.Infrastructure.UsageTracking public async Task TrackUsagesAsync(params UsageUpdate[] updates) { + Guard.NotNull(updates, nameof(updates)); + if (updates.Length == 1) { await TrackUsagesAsync(updates[0]); diff --git a/backend/src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs b/backend/src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs index 45cda82b3..c8add61f2 100644 --- a/backend/src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs +++ b/backend/src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs @@ -39,11 +39,6 @@ namespace Squidex.Infrastructure.CQRS.Events public RabbitMqEventConsumer(IJsonSerializer jsonSerializer, string eventPublisherName, string uri, string exchange, string eventsFilter) { - Guard.NotNullOrEmpty(uri, nameof(uri)); - Guard.NotNullOrEmpty(eventPublisherName, nameof(eventPublisherName)); - Guard.NotNullOrEmpty(exchange, nameof(exchange)); - Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); - connectionFactory = new ConnectionFactory { Uri = new Uri(uri, UriKind.Absolute) }; connection = new Lazy(connectionFactory.CreateConnection); channel = new Lazy(connection.Value.CreateModel); diff --git a/backend/src/Squidex.Infrastructure.RabbitMq/Squidex.Infrastructure.RabbitMq.csproj b/backend/src/Squidex.Infrastructure.RabbitMq/Squidex.Infrastructure.RabbitMq.csproj index fd84995c0..36ff6a906 100644 --- a/backend/src/Squidex.Infrastructure.RabbitMq/Squidex.Infrastructure.RabbitMq.csproj +++ b/backend/src/Squidex.Infrastructure.RabbitMq/Squidex.Infrastructure.RabbitMq.csproj @@ -10,7 +10,7 @@ True - + diff --git a/backend/src/Squidex.Infrastructure.Redis/RedisPubSub.cs b/backend/src/Squidex.Infrastructure.Redis/RedisPubSub.cs index 74ef5c100..b6bd45ec0 100644 --- a/backend/src/Squidex.Infrastructure.Redis/RedisPubSub.cs +++ b/backend/src/Squidex.Infrastructure.Redis/RedisPubSub.cs @@ -26,10 +26,6 @@ namespace Squidex.Infrastructure public RedisPubSub(IConnectionMultiplexer redis, IJsonSerializer serializer, ISemanticLog log) { - Guard.NotNull(serializer, nameof(serializer)); - Guard.NotNull(redis, nameof(redis)); - Guard.NotNull(log, nameof(log)); - this.log = log; this.redis = redis; this.serializer = serializer; diff --git a/backend/src/Squidex.Infrastructure/Commands/CustomCommandMiddlewareRunner.cs b/backend/src/Squidex.Infrastructure/Commands/CustomCommandMiddlewareRunner.cs index 7375e871b..f5a126a13 100644 --- a/backend/src/Squidex.Infrastructure/Commands/CustomCommandMiddlewareRunner.cs +++ b/backend/src/Squidex.Infrastructure/Commands/CustomCommandMiddlewareRunner.cs @@ -17,8 +17,6 @@ namespace Squidex.Infrastructure.Commands public CustomCommandMiddlewareRunner(IEnumerable extensions) { - Guard.NotNull(extensions, nameof(extensions)); - this.extensions = extensions.Reverse().ToList(); } diff --git a/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs b/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs index 12b1e7b2e..13161c4ad 100644 --- a/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs +++ b/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs @@ -217,6 +217,8 @@ namespace Squidex.Infrastructure.Commands { Guard.NotNull(handler, nameof(handler)); + var wasDeleted = IsDeleted(); + var previousSnapshot = Snapshot; var previousVersion = Version; try @@ -233,7 +235,7 @@ namespace Squidex.Infrastructure.Commands { await EnsureLoadedAsync(true); - if (IsDeleted()) + if (wasDeleted) { if (CanRecreate() && isCreation) { @@ -304,9 +306,19 @@ namespace Squidex.Infrastructure.Commands { var newVersion = Version + 1; - var snapshotNew = Apply(Snapshot, @event); + var snapshotOld = Snapshot; + + if (IsDeleted()) + { + snapshotOld = new T + { + Version = Version + }; + } + + var snapshotNew = Apply(snapshotOld, @event); - if (!ReferenceEquals(Snapshot, snapshotNew) || isLoading) + if (!ReferenceEquals(snapshotOld, snapshotNew) || isLoading) { snapshotNew.Version = newVersion; snapshots.Add(snapshotNew, newVersion, true); diff --git a/backend/src/Squidex.Infrastructure/Commands/EnrichWithTimestampCommandMiddleware.cs b/backend/src/Squidex.Infrastructure/Commands/EnrichWithTimestampCommandMiddleware.cs index 99a283796..d892fe89b 100644 --- a/backend/src/Squidex.Infrastructure/Commands/EnrichWithTimestampCommandMiddleware.cs +++ b/backend/src/Squidex.Infrastructure/Commands/EnrichWithTimestampCommandMiddleware.cs @@ -16,8 +16,6 @@ namespace Squidex.Infrastructure.Commands public EnrichWithTimestampCommandMiddleware(IClock clock) { - Guard.NotNull(clock, nameof(clock)); - this.clock = clock; } diff --git a/backend/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs b/backend/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs index 7b73acc6a..8a9b93df0 100644 --- a/backend/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs +++ b/backend/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs @@ -16,8 +16,6 @@ namespace Squidex.Infrastructure.Commands public GrainCommandMiddleware(IGrainFactory grainFactory) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - this.grainFactory = grainFactory; } diff --git a/backend/src/Squidex.Infrastructure/Commands/InMemoryCommandBus.cs b/backend/src/Squidex.Infrastructure/Commands/InMemoryCommandBus.cs index 75c96da9b..9d776c4d8 100644 --- a/backend/src/Squidex.Infrastructure/Commands/InMemoryCommandBus.cs +++ b/backend/src/Squidex.Infrastructure/Commands/InMemoryCommandBus.cs @@ -48,8 +48,6 @@ namespace Squidex.Infrastructure.Commands public InMemoryCommandBus(IEnumerable middlewares) { - Guard.NotNull(middlewares, nameof(middlewares)); - var reverseMiddlewares = middlewares.Reverse().ToList(); IStep next = new NoopStep(); diff --git a/backend/src/Squidex.Infrastructure/Commands/LogCommandMiddleware.cs b/backend/src/Squidex.Infrastructure/Commands/LogCommandMiddleware.cs index 969c9a8d0..413540f79 100644 --- a/backend/src/Squidex.Infrastructure/Commands/LogCommandMiddleware.cs +++ b/backend/src/Squidex.Infrastructure/Commands/LogCommandMiddleware.cs @@ -17,8 +17,6 @@ namespace Squidex.Infrastructure.Commands public LogCommandMiddleware(ISemanticLog log) { - Guard.NotNull(log, nameof(log)); - this.log = log; } diff --git a/backend/src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs b/backend/src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs index b0ef7fa10..cbecab9b3 100644 --- a/backend/src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs +++ b/backend/src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs @@ -17,8 +17,6 @@ namespace Squidex.Infrastructure.Commands public ReadonlyCommandMiddleware(IOptions options) { - Guard.NotNull(options, nameof(options)); - this.options = options.Value; } diff --git a/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs b/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs index 454c44fec..b2fec2225 100644 --- a/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs +++ b/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs @@ -40,10 +40,6 @@ namespace Squidex.Infrastructure.Commands IEventStore eventStore, IServiceProvider serviceProvider) { - Guard.NotNull(localCache, nameof(localCache)); - Guard.NotNull(serviceProvider, nameof(serviceProvider)); - Guard.NotNull(eventStore, nameof(eventStore)); - this.eventStore = eventStore; this.serviceProvider = serviceProvider; this.localCache = localCache; diff --git a/backend/src/Squidex.Infrastructure/Diagnostics/GCHealthCheck.cs b/backend/src/Squidex.Infrastructure/Diagnostics/GCHealthCheck.cs index 98940e086..234784986 100644 --- a/backend/src/Squidex.Infrastructure/Diagnostics/GCHealthCheck.cs +++ b/backend/src/Squidex.Infrastructure/Diagnostics/GCHealthCheck.cs @@ -20,8 +20,6 @@ namespace Squidex.Infrastructure.Diagnostics public GCHealthCheck(IOptions options) { - Guard.NotNull(options, nameof(options)); - threshold = 1024 * 1024 * options.Value.Threshold; } diff --git a/backend/src/Squidex.Infrastructure/Diagnostics/OrleansHealthCheck.cs b/backend/src/Squidex.Infrastructure/Diagnostics/OrleansHealthCheck.cs index e80ad1c83..698574412 100644 --- a/backend/src/Squidex.Infrastructure/Diagnostics/OrleansHealthCheck.cs +++ b/backend/src/Squidex.Infrastructure/Diagnostics/OrleansHealthCheck.cs @@ -19,8 +19,6 @@ namespace Squidex.Infrastructure.Diagnostics public OrleansHealthCheck(IGrainFactory grainFactory) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - managementGrain = grainFactory.GetGrain(0); } diff --git a/backend/src/Squidex.Infrastructure/Email/SmtpEmailSender.cs b/backend/src/Squidex.Infrastructure/Email/SmtpEmailSender.cs index 30ce0157c..bf1dd89c8 100644 --- a/backend/src/Squidex.Infrastructure/Email/SmtpEmailSender.cs +++ b/backend/src/Squidex.Infrastructure/Email/SmtpEmailSender.cs @@ -24,8 +24,6 @@ namespace Squidex.Infrastructure.Email public SmtpEmailSender(IOptions options) { - Guard.NotNull(options, nameof(options)); - this.options = options.Value; clientPool = new DefaultObjectPoolProvider().Create(new DefaultPooledObjectPolicy()); diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs b/backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs index ea71efbdb..4afdba771 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs @@ -20,9 +20,6 @@ namespace Squidex.Infrastructure.EventSourcing public DefaultEventDataFormatter(TypeNameRegistry typeNameRegistry, IJsonSerializer serializer) { - Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); - Guard.NotNull(serializer, nameof(serializer)); - this.typeNameRegistry = typeNameRegistry; this.serializer = serializer; diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/EventConsumersHealthCheck.cs b/backend/src/Squidex.Infrastructure/EventSourcing/EventConsumersHealthCheck.cs index ee77093c2..019ee25e8 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/EventConsumersHealthCheck.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/EventConsumersHealthCheck.cs @@ -21,8 +21,6 @@ namespace Squidex.Infrastructure.EventSourcing public EventConsumersHealthCheck(IGrainFactory grainFactory) { - Guard.NotNull(grainFactory, nameof(grainFactory)); - this.grainFactory = grainFactory; } diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs b/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs index d99ef13cd..2a755855f 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs @@ -41,12 +41,6 @@ namespace Squidex.Infrastructure.EventSourcing.Grains IEventDataFormatter eventDataFormatter, ISemanticLog log) { - Guard.NotNull(eventStore, nameof(eventStore)); - Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); - Guard.NotNull(eventConsumerFactory, nameof(eventConsumerFactory)); - Guard.NotNull(state, nameof(state)); - Guard.NotNull(log, nameof(log)); - this.eventStore = eventStore; this.eventDataFormatter = eventDataFormatter; this.eventConsumerFactory = eventConsumerFactory; @@ -101,7 +95,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await DispatchAsync(events); State = State.Handled(position, events.Count); - }); + }, State.Position); } public Task OnErrorAsync(object sender, Exception exception) @@ -116,7 +110,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains Unsubscribe(); State = State.Stopped(exception); - }); + }, State.Position); } public async Task ActivateAsync() @@ -128,7 +122,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains Subscribe(); State = State.Started(); - }); + }, State.Position); } else if (!State.IsStopped) { @@ -148,7 +142,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains Subscribe(); State = State.Started(); - }); + }, State.Position); return CreateInfo(); } @@ -165,7 +159,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains Unsubscribe(); State = State.Stopped(); - }); + }, State.Position); return CreateInfo(); } @@ -178,10 +172,10 @@ namespace Squidex.Infrastructure.EventSourcing.Grains await ClearAsync(); - State = EventConsumerState.Reset(); + State = EventConsumerState.Initial; Subscribe(); - }); + }, State.Position); return CreateInfo(); } @@ -194,17 +188,17 @@ namespace Squidex.Infrastructure.EventSourcing.Grains } } - private Task DoAndUpdateStateAsync(Action action, [CallerMemberName] string? caller = null) + private Task DoAndUpdateStateAsync(Action action, string? position, [CallerMemberName] string? caller = null) { return DoAndUpdateStateAsync(() => { action(); return Task.CompletedTask; - }, caller); + }, position, caller); } - private async Task DoAndUpdateStateAsync(Func action, [CallerMemberName] string? caller = null) + private async Task DoAndUpdateStateAsync(Func action, string? position, [CallerMemberName] string? caller = null) { await semaphore.WaitAsync(); try @@ -229,6 +223,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains log.LogFatal(ex, w => w .WriteProperty("action", caller) .WriteProperty("status", "Failed") + .WriteProperty("eventPosition", position) .WriteProperty("eventConsumer", eventConsumer!.Name)); State = previousState.Stopped(ex); diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs b/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs index 5841e66f1..6aaedbecb 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs @@ -32,8 +32,6 @@ namespace Squidex.Infrastructure.EventSourcing.Grains IGrainRuntime? runtime) : base(identity, runtime) { - Guard.NotNull(eventConsumers, nameof(eventConsumers)); - this.eventConsumers = eventConsumers; } diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerState.cs b/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerState.cs index 84a943dff..32461ecfc 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerState.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerState.cs @@ -12,13 +12,15 @@ namespace Squidex.Infrastructure.EventSourcing.Grains { public sealed class EventConsumerState { - public bool IsStopped { get; set; } + public static readonly EventConsumerState Initial = new EventConsumerState(); - public int Count { get; set; } + public bool IsStopped { get; init; } - public string? Error { get; set; } + public string? Error { get; init; } - public string? Position { get; set; } + public string? Position { get; init; } + + public int Count { get; init; } public bool IsPaused { @@ -41,11 +43,6 @@ namespace Squidex.Infrastructure.EventSourcing.Grains Count = count; } - public static EventConsumerState Reset() - { - return new EventConsumerState(); - } - public EventConsumerState Handled(string position, int offset = 1) { return new EventConsumerState(position, Count + offset); @@ -58,7 +55,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains public EventConsumerState Started() { - return new EventConsumerState(Position, Count) { IsStopped = false }; + return new EventConsumerState(Position, Count); } public EventConsumerInfo ToInfo(string name) diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/Grains/OrleansEventNotifier.cs b/backend/src/Squidex.Infrastructure/EventSourcing/Grains/OrleansEventNotifier.cs index 190f030a7..49fc86a26 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/Grains/OrleansEventNotifier.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/Grains/OrleansEventNotifier.cs @@ -17,8 +17,6 @@ namespace Squidex.Infrastructure.EventSourcing.Grains public OrleansEventNotifier(IGrainFactory factory) { - Guard.NotNull(factory, nameof(factory)); - eventConsumerManagerGrain = new Lazy(() => { return factory.GetGrain(SingleGrain.Id); diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/PollingSubscription.cs b/backend/src/Squidex.Infrastructure/EventSourcing/PollingSubscription.cs index 4864762bc..98196d1bf 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/PollingSubscription.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/PollingSubscription.cs @@ -21,9 +21,6 @@ namespace Squidex.Infrastructure.EventSourcing string? streamFilter, string? position) { - Guard.NotNull(eventStore, nameof(eventStore)); - Guard.NotNull(eventSubscriber, nameof(eventSubscriber)); - timer = new CompletionTimer(5000, async ct => { try diff --git a/backend/src/Squidex.Infrastructure/Json/Newtonsoft/TypeNameSerializationBinder.cs b/backend/src/Squidex.Infrastructure/Json/Newtonsoft/TypeNameSerializationBinder.cs index b6b484f80..00dabb905 100644 --- a/backend/src/Squidex.Infrastructure/Json/Newtonsoft/TypeNameSerializationBinder.cs +++ b/backend/src/Squidex.Infrastructure/Json/Newtonsoft/TypeNameSerializationBinder.cs @@ -17,8 +17,6 @@ namespace Squidex.Infrastructure.Json.Newtonsoft public TypeNameSerializationBinder(TypeNameRegistry typeNameRegistry) { - Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); - this.typeNameRegistry = typeNameRegistry; } diff --git a/backend/src/Squidex.Infrastructure/Log/BackgroundRequestLogStore.cs b/backend/src/Squidex.Infrastructure/Log/BackgroundRequestLogStore.cs index 0a765b168..d6ed93ddc 100644 --- a/backend/src/Squidex.Infrastructure/Log/BackgroundRequestLogStore.cs +++ b/backend/src/Squidex.Infrastructure/Log/BackgroundRequestLogStore.cs @@ -32,10 +32,6 @@ namespace Squidex.Infrastructure.Log public BackgroundRequestLogStore(IOptions options, IRequestLogRepository logRepository, ISemanticLog log) { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(logRepository, nameof(logRepository)); - Guard.NotNull(log, nameof(log)); - this.options = options.Value; this.logRepository = logRepository; diff --git a/backend/src/Squidex.Infrastructure/Migrations/Migrator.cs b/backend/src/Squidex.Infrastructure/Migrations/Migrator.cs index 133353ba5..98189076a 100644 --- a/backend/src/Squidex.Infrastructure/Migrations/Migrator.cs +++ b/backend/src/Squidex.Infrastructure/Migrations/Migrator.cs @@ -23,10 +23,6 @@ namespace Squidex.Infrastructure.Migrations public Migrator(IMigrationStatus migrationStatus, IMigrationPath migrationPath, ISemanticLog log) { - Guard.NotNull(migrationStatus, nameof(migrationStatus)); - Guard.NotNull(migrationPath, nameof(migrationPath)); - Guard.NotNull(log, nameof(log)); - this.migrationStatus = migrationStatus; this.migrationPath = migrationPath; diff --git a/backend/src/Squidex.Infrastructure/ObjectPool/DefaultPools.cs b/backend/src/Squidex.Infrastructure/ObjectPool/DefaultPools.cs index 7335bc38e..c782f0aff 100644 --- a/backend/src/Squidex.Infrastructure/ObjectPool/DefaultPools.cs +++ b/backend/src/Squidex.Infrastructure/ObjectPool/DefaultPools.cs @@ -5,16 +5,16 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.IO; using System.Text; using Microsoft.Extensions.ObjectPool; +using Microsoft.IO; namespace Squidex.Infrastructure.ObjectPool { public static class DefaultPools { - public static readonly ObjectPool MemoryStream = - new DefaultObjectPool(new MemoryStreamPooledObjectPolicy()); + public static readonly RecyclableMemoryStreamManager MemoryStream = + new RecyclableMemoryStreamManager(); public static readonly ObjectPool StringBuilder = new DefaultObjectPool(new StringBuilderPooledObjectPolicy()); diff --git a/backend/src/Squidex.Infrastructure/ObjectPool/MemoryStreamPooledObjectPolicy.cs b/backend/src/Squidex.Infrastructure/ObjectPool/MemoryStreamPooledObjectPolicy.cs deleted file mode 100644 index 53a44fc6a..000000000 --- a/backend/src/Squidex.Infrastructure/ObjectPool/MemoryStreamPooledObjectPolicy.cs +++ /dev/null @@ -1,36 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.IO; -using Microsoft.Extensions.ObjectPool; - -namespace Squidex.Infrastructure.ObjectPool -{ - public sealed class MemoryStreamPooledObjectPolicy : PooledObjectPolicy - { - public int InitialCapacity { get; set; } = 100; - - public int MaximumRetainedCapacity { get; set; } = 4 * 1024; - - public override MemoryStream Create() - { - return new MemoryStream(InitialCapacity); - } - - public override bool Return(MemoryStream obj) - { - if (obj.Capacity > MaximumRetainedCapacity) - { - return false; - } - - obj.Position = 0; - - return true; - } - } -} \ No newline at end of file diff --git a/backend/src/Squidex.Infrastructure/Orleans/ActivationLimit.cs b/backend/src/Squidex.Infrastructure/Orleans/ActivationLimit.cs index fd261a7c8..38f1bb17f 100644 --- a/backend/src/Squidex.Infrastructure/Orleans/ActivationLimit.cs +++ b/backend/src/Squidex.Infrastructure/Orleans/ActivationLimit.cs @@ -21,9 +21,6 @@ namespace Squidex.Infrastructure.Orleans public ActivationLimit(IGrainActivationContext context, IActivationLimiter limiter) { - Guard.NotNull(context, nameof(context)); - Guard.NotNull(limiter, nameof(limiter)); - this.context = context; this.limiter = limiter; } diff --git a/backend/src/Squidex.Infrastructure/Orleans/Indexes/IdsIndexGrain.cs b/backend/src/Squidex.Infrastructure/Orleans/Indexes/IdsIndexGrain.cs index fdae2ceb2..c062e7e2c 100644 --- a/backend/src/Squidex.Infrastructure/Orleans/Indexes/IdsIndexGrain.cs +++ b/backend/src/Squidex.Infrastructure/Orleans/Indexes/IdsIndexGrain.cs @@ -18,8 +18,6 @@ namespace Squidex.Infrastructure.Orleans.Indexes public IdsIndexGrain(IGrainState state) { - Guard.NotNull(state, nameof(state)); - this.state = state; } diff --git a/backend/src/Squidex.Infrastructure/Orleans/Indexes/UniqueNameIndexGrain.cs b/backend/src/Squidex.Infrastructure/Orleans/Indexes/UniqueNameIndexGrain.cs index b55354beb..3ae13f609 100644 --- a/backend/src/Squidex.Infrastructure/Orleans/Indexes/UniqueNameIndexGrain.cs +++ b/backend/src/Squidex.Infrastructure/Orleans/Indexes/UniqueNameIndexGrain.cs @@ -19,8 +19,6 @@ namespace Squidex.Infrastructure.Orleans.Indexes public UniqueNameIndexGrain(IGrainState state) { - Guard.NotNull(state, nameof(state)); - this.state = state; } diff --git a/backend/src/Squidex.Infrastructure/Orleans/LocalCacheFilter.cs b/backend/src/Squidex.Infrastructure/Orleans/LocalCacheFilter.cs index e839eeb37..8ef6e31d7 100644 --- a/backend/src/Squidex.Infrastructure/Orleans/LocalCacheFilter.cs +++ b/backend/src/Squidex.Infrastructure/Orleans/LocalCacheFilter.cs @@ -18,8 +18,6 @@ namespace Squidex.Infrastructure.Orleans public LocalCacheFilter(ILocalCache localCache) { - Guard.NotNull(localCache, nameof(localCache)); - this.localCache = localCache; } diff --git a/backend/src/Squidex.Infrastructure/Orleans/LoggingFilter.cs b/backend/src/Squidex.Infrastructure/Orleans/LoggingFilter.cs index 7079723ab..d9944edc9 100644 --- a/backend/src/Squidex.Infrastructure/Orleans/LoggingFilter.cs +++ b/backend/src/Squidex.Infrastructure/Orleans/LoggingFilter.cs @@ -18,8 +18,6 @@ namespace Squidex.Infrastructure.Orleans public LoggingFilter(ISemanticLog log) { - Guard.NotNull(log, nameof(log)); - this.log = log; } diff --git a/backend/src/Squidex.Infrastructure/Orleans/StateFilter.cs b/backend/src/Squidex.Infrastructure/Orleans/StateFilter.cs index 78332e218..73a2e69e1 100644 --- a/backend/src/Squidex.Infrastructure/Orleans/StateFilter.cs +++ b/backend/src/Squidex.Infrastructure/Orleans/StateFilter.cs @@ -19,8 +19,6 @@ namespace Squidex.Infrastructure.Orleans public StateFilter(IGrainRuntime runtime) { - Guard.NotNull(runtime, nameof(runtime)); - this.runtime = runtime; } diff --git a/backend/src/Squidex.Infrastructure/Security/Extensions.cs b/backend/src/Squidex.Infrastructure/Security/Extensions.cs index d359f96cd..332f202ba 100644 --- a/backend/src/Squidex.Infrastructure/Security/Extensions.cs +++ b/backend/src/Squidex.Infrastructure/Security/Extensions.cs @@ -16,17 +16,18 @@ namespace Squidex.Infrastructure.Security public static RefToken? Token(this ClaimsPrincipal principal) { var subjectId = principal.OpenIdSubject(); + var subjectName = principal.OpenIdName(); var clientId = principal.OpenIdClientId(); - if (!string.IsNullOrWhiteSpace(clientId) && (string.Equals(clientId, subjectId, StringComparison.Ordinal) || string.IsNullOrWhiteSpace(subjectId))) + if (!string.IsNullOrWhiteSpace(subjectId) && !string.IsNullOrWhiteSpace(subjectName)) { - return RefToken.Client(clientId); + return RefToken.User(subjectId); } - if (!string.IsNullOrWhiteSpace(subjectId)) + if (!string.IsNullOrWhiteSpace(clientId)) { - return RefToken.User(subjectId); + return RefToken.Client(clientId); } return null; @@ -57,11 +58,6 @@ namespace Squidex.Infrastructure.Security return principal.Claims.FirstOrDefault(x => x.Type == OpenIdClaims.Name)?.Value; } - public static string? OpenIdNickName(this ClaimsPrincipal principal) - { - return principal.Claims.FirstOrDefault(x => x.Type == OpenIdClaims.NickName)?.Value; - } - public static string? OpenIdEmail(this ClaimsPrincipal principal) { return principal.Claims.FirstOrDefault(x => x.Type == OpenIdClaims.Email)?.Value; diff --git a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj index 9d523bccf..9ea21eb73 100644 --- a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj +++ b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj @@ -10,25 +10,26 @@ - - + + - - - + + + + all runtime; build; native; contentfiles; analyzers - - + + - + - + - + diff --git a/backend/src/Squidex.Infrastructure/States/Store.cs b/backend/src/Squidex.Infrastructure/States/Store.cs index f73fef2cc..ab1982237 100644 --- a/backend/src/Squidex.Infrastructure/States/Store.cs +++ b/backend/src/Squidex.Infrastructure/States/Store.cs @@ -24,11 +24,6 @@ namespace Squidex.Infrastructure.States IEventDataFormatter eventDataFormatter, IStreamNameResolver streamNameResolver) { - Guard.NotNull(snapshotStore, nameof(snapshotStore)); - Guard.NotNull(eventStore, nameof(eventStore)); - Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); - Guard.NotNull(streamNameResolver, nameof(streamNameResolver)); - this.snapshotStore = snapshotStore; this.eventStore = eventStore; this.eventDataFormatter = eventDataFormatter; diff --git a/backend/src/Squidex.Infrastructure/Translations/ResourcesLocalizer.cs b/backend/src/Squidex.Infrastructure/Translations/ResourcesLocalizer.cs index 102d3177f..df345ceba 100644 --- a/backend/src/Squidex.Infrastructure/Translations/ResourcesLocalizer.cs +++ b/backend/src/Squidex.Infrastructure/Translations/ResourcesLocalizer.cs @@ -21,8 +21,6 @@ namespace Squidex.Infrastructure.Translations public ResourcesLocalizer(ResourceManager resourceManager) { - Guard.NotNull(resourceManager, nameof(resourceManager)); - this.resourceManager = resourceManager; } diff --git a/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs b/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs index bb775b16f..e0815a25d 100644 --- a/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs +++ b/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs @@ -27,9 +27,6 @@ namespace Squidex.Infrastructure.UsageTracking public BackgroundUsageTracker(IUsageRepository usageRepository, ISemanticLog log) { - Guard.NotNull(usageRepository, nameof(usageRepository)); - Guard.NotNull(log, nameof(log)); - this.usageRepository = usageRepository; this.log = log; diff --git a/backend/src/Squidex.Shared/Texts.zh.resx b/backend/src/Squidex.Shared/Texts.zh.resx new file mode 100644 index 000000000..1fe9aca84 --- /dev/null +++ b/backend/src/Squidex.Shared/Texts.zh.resx @@ -0,0 +1,1204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 字段 '{name|lower}' 必须是绝对 URL。 + + + 字段 '{0}' 必须是绝对 URL。 + + + 字段 '{name|lower}' 必须与 {other|lower} 相同。 + + + 字段 '{0}' 必须与 {1} 相同。 + + + 字段'{name|lower}' 不是有效的电子邮件地址。 + + + 字段'{0}' 不是有效的电子邮件地址。 + + + 字段'{name|lower}' 必须介于 {min} 和 {max} 之间。 + + + 字段'{0}' 必须介于 {1} 和 {2} 之间。 + + + 字段 '{name|lower}' 无法通过表达式验证 + + + 字段 '{0}' 无法通过表达式验证 + + + 字段'{name|lower}' 是必需的。 + + + 字段'{0}' 是必需的。 + + + 字段'{name|lower}' 必须是最大长度为 {max} 的字符串。 + + + 字段'{0}' 必须是最大长度为 {1} 的字符串。 + + + 字段'{name|lower}' 必须是一个字符串,最小长度为 {min},最大长度为 {max}。 + + + 字段'{0}' 必须是一个字符串,最小长度为 {1},最大长度为 {2}。 + + + 已存在具有相同 ID 的客户端。 + + + 你不能改变你自己的角色。 + + + 您已达到计划的最大贡献者数量。 + + + 无法删除唯一的所有者。 + + + 应用没有后备语言'{fallback}'。 + + + 语言已经添加。 + + + 主语言不能有后备语言。 + + + 主语言不能是可选的。 + + + 无法删除主语言。 + + + 同名应用已经存在。 + + + 文件不是图像 + + + 不存在具有此 ID 的计划。 + + + 计划只能由最初配置计划的用户更改。 + + + 无法删除默认角色。 + + + 无法更新默认角色。 + + + 已存在同名角色。 + + + 分配客户端后无法删除角色。 + + + 当分配了贡献者时无法删除角色。 + + + 资源文件夹不存在。 + + + 无法将文件夹添加到自己的子文件夹中。 + + + 您已达到最大资源大小。 + + + 资源被内容引用,无法删除。 + + + 另一个备份进程已经在运行。 + + + 您不能拥有超过 {max} 个备份。 + + + 还原操作已经在运行。 + + + 您只能访问您的通知。 + + + 评论是由另一个用户创建的。 + + + 动作 + + + 纵横高度 + + + 纵横宽 + + + 计算的默认值 + + + 客户端 ID + + + 客户端 ID + + + 客户端密码 + + + 内容类型 + + + 贡献者 ID 或电子邮件 + + + 关键 + + + 数据 + + + 默认值 + + + 显示名称 + + + 编辑器 + + + 电子邮件 + + + 您没有必要的权限。 + + + 字段 + + + 字段 ID + + + 字段名 + + + 文件 + + + 文件夹名称 + + + 不支持查询搜索子句。 + + + 文件内容类型未定义。 + + + 文件名未定义。 + + + 模型无效。 + + + 请求正文的格式无效。 + + + 客户端不允许。 + + + 验证错误 + + + 初始步骤 + + + 执行脚本失败,Javascript 错误:{message} + + + 脚本禁止操作。 + + + 无法执行脚本,Javascript 语法错误:{message} + + + 脚本拒绝了操作。 + + + 语言代码 + + + 登录 + + + 注销 + + + 最大字符数 + + + 最大高度 + + + 最大项目数 + + + 最大长度 + + + 最大尺寸 + + + 最大值 + + + 最大宽度 + + + 最大字数 + + + 最小字符数 + + + 最小高度 + + + 最小项目 + + + 最小长度 + + + 最小尺寸 + + + 最小值 + + + 最小宽度 + + + 最小词 + + + 名称 + + + - 未找到 - + + + 天数 + + + 无法解析查询:{message},查询:{odata} + + + OData $filter 子句无效:{message} + + + 查询不支持 OData 操作:{odata} + + + OData $search 子句无效:{message} + + + 旧密码 + + + 其他 + + + 分区 + + + 密码 + + + 确认 + + + 模式 + + + 权限 + + + 计划 ID + + + 预览网址 + + + Squidex Headless CMS + + + 属性 + + + 属性 + + + 应用程序目前处于只读模式。 + + + 删除 + + + 结果集太大无法检索。使用 $take 参数减少项目数。 + + + 角色 + + + 保存 + + + Schemas ID + + + 注册 + + + 成功 + + + 文本 + + + 触发器 + + + 警告 + + + 工作流程 + + + 步骤 + + + 过渡 + + + 多个内容与查询匹配。 + + + 您只能在内容发布后创建新版本。 + + + 没有要删除的内容。 + + + 无效的 json 类型,预期的对象数组。 + + + 无效的 json 类型,需要字符串数组。 + + + 无效的 json 类型,应为布尔值。 + + + 无效的 json 对象,带有 'schemaId' 字段的预期对象。 + + + 无效组件。未找到 'schemaId' 字段。 + + + 无效组件。找不到Schemas。 + + + 无效的 json 类型,预期的纬度/经度对象。 + + + 纬度必须在 -90 到 90 之间。 + + + 经度必须在 -180 到 180 之间。 + + + 无效的 json 类型,需要的数字。 + + + 无效的 json 类型,需要的字符串。 + + + {count} 个引用 + + + 内容被其他内容引用,无法删除或取消发布。 + + + Schemas未发布。 + + + 无法更新单个内容。 + + + 无法创建单个内容。 + + + 无法删除单个内容。 + + + 工作流中未定义状态。 + + + 无法将状态从 {oldStatus} 更改为 {newStatus}。 + + + 必须有纵横比 {width}:{height}。 + + + 未找到 ID {id}。 + + + 必须介于 {min} 和 {max} 之间。 + + + 必须正好有 {count} 个字符。 + + + 必须在 {min} 到 {max} 个字符之间。 + + + 不得包含重复值。 + + + 验证失败,内部错误。 + + + 必须正好是 {value}。 + + + 必须是允许的扩展名。 + + + 不是图片。 + + + 无效值。 + + + 必须正好有 {count} 个项目。 + + + 必须介于 {min} 和 {max} 项之间。 + + + 必须小于或等于 {max}。 + + + 不得超过 {max} 个文本字符。 + + + 高度 {height}px 必须小于 {max}px。 + + + {size} 的大小必须小于 {max}。 + + + 宽度 {width}px 必须小于 {max}px。 + + + 不得超过 {max} 个项目。 + + + 不得超过 {max} 个字符。 + + + 不得超过 {max} 个单词。 + + + 必须大于或等于 {min}。 + + + 高度 {height}px 必须大于 {min}px。 + + + {size} 的大小必须大于 {min}。 + + + 宽度 {width}px 必须大于 {min}px。 + + + 必须至少有 {min} 个项目。 + + + 必须至少有 {min} 个字符。 + + + 必须至少有 {min} 个文本字符。 + + + 必须至少有 {min} 个单词。 + + + 不得定义值。 + + + 必须正好有 {count} 个文本字符。 + + + 必须在 {min} 和 {max} 个文本字符之间。 + + + 不允许的值。 + + + 必须遵循模式。 + + + 未找到引用 '{id}'。 + + + 参考 '{id}' 的Schemas无效。 + + + 正则表达式太慢了。 + + + 必填字段。 + + + 存在另一个具有相同值的内容。 + + + 不是已知的 {fieldType}。 + + + 必须正好有 {count} 个单词。 + + + 必须在 {min} 到 {max} 个单词之间。 + + + 工作流不允许更新状态为 {status} + + + 发生未知故障。 + + + 电子邮件已被占用。 + + + 用户名已被占用。 + + + 电子邮件无效。 + + + 用户名“{0}”无效,只能包含字母或数字。 + + + 已存在使用此登录名的用户。 + + + 密码不正确。 + + + 密码必须至少有一位 ('0'-'9')。 + + + 密码必须至少有一个小写字母 ('a'-'z')。 + + + 密码必须至少有一个非字母数字字符。 + + + 密码必须至少使用 {0} 个不同的字符。 + + + 密码必须至少有一个大写字母 ('A'-'Z')。 + + + 密码太短。 + + + 此密码以前曾出现在数据泄露事件中,永远不应使用。如果您以前曾在任何地方使用过它,请更改它! + + + User is locked out. + + + Json 查询无效:{message} + + + Json 查询无效 json: {message} + + + 实体 ({id}) 已经存在。 + + + 实体 ({id}) 已被删除。 + + + 实体 ({id}) 不存在。 + + + 实体 ({id}) 请求版本 {expectedVersion},但找到 {currentVersion}。 + + + 将客户端 {[Id]} 添加到应用程序 + + + 已撤销客户端 {[Id]} + + + 更新的客户端 {[Id]} + + + 已分配 {user:[Contributor]} 作为 {[Role]} + + + 从应用中删除了 {user:[Contributor]} + + + 添加语言 {[Language]} + + + 已删除语言 {[Language]} + + + 将主语言更改为 {[Language]} + + + 更新的语言 {[Language]} + + + 已将计划更改为 {[Plan]} + + + 重置计划 + + + 添加角色 {[Name]} + + + 已删除角色 {[Name]} + + + 更新角色 {[Name]} + + + 更新的 UI 设置 + + + 替换的资源。 + + + 更新的资源。 + + + 上传的资源。 + + + 创建了 {[Schema]} 内容。 + + + 删除了 {[Schema]} 内容。 + + + 创建了新草稿。 + + + 已删除的草稿。 + + + 已安排将 {[Schema]} 内容的状态更改为 {[Status]}。 + + + 无法安排 {[Schema]} 内容的状态更改。 + + + 更新了 {[Schema]} 内容。 + + + 创建的Schemas {[Name]}。 + + + 已删除Schemas {[Name]}。 + + + 将字段 {[Field]} 添加到Schemas {[Name]}。 + + + 已从Schemas {[Name]} 中删除字段 {[Field]}。 + + + Schemas {[Name]} 的禁用字段 {[Field]}。 + + + 有Schemas {[Name]} 的隐藏字段 {[Field]}。 + + + 已锁定Schemas {[Name]} 的字段 {[Field]}。 + + + 已显示Schemas {[Name]} 的字段 {[Field]}。 + + + Schemas {[Name]} 的重新排序字段。 + + + 已更新Schemas {[Name]} 的字段 {[Field]}。 + + + 已发布的Schemas {[Name]}。 + + + Schemas {[Name]} 的配置脚本。 + + + 未发布的Schemas {[Name]}。 + + + 更新的Schemas {[Name]}。 + + + 已将 {[Schema]} 内容的状态更改为 {[Status]}。 + + + 您的邮箱在 Github 中设置为私有。请设置为公开以使用 Github 登录。 + + + 另一个规则已经在运行。 + + + 计算出的默认值和默认值不能一起使用。 + + + 字段 '{field}' 已添加两次。 + + + 字段不能是 UI 字段。 + + + Schema field is locked. + + + 已存在同名字段。 + + + Field is not part of the schema. + + + 字段 ID 未涵盖所有字段。 + + + 一个同名的Schemas已经存在。 + + + You do not have permission for this schema. + + + Schema {id} does not exist. + + + 无线电编辑器不允许内联编辑。 + + + 只有数组字段可以有嵌套字段。 + + + 嵌套字段不能是数组字段。 + + + 只有在 MaxItems 为 1 时才能解析引用。 + + + 内联编辑只允许用于下拉菜单、slug 和输入字段。 + + + 单选按钮或下拉列表需要允许的值。 + + + 复选框或下拉列表需要允许的值。 + + + UI 字段不能被禁用。 + + + UI 字段无法启用。 + + + UI 字段不能隐藏。 + + + UI 字段无法显示。 + + + {name} 内容 + + + {name} 内容 + + + {name} Schemas + + + 此密码以前曾出现在数据泄露事件中,永远不应使用。如果您以前曾在任何地方使用过它,请更改它! + + + 创建用户 + + + 确认 + + + 既未配置密码认证,也未配置 Google 等外部认证提供商。请检查您的设置和日志。 + + + 管理员用户 + + + 创建管理员用户 + + + 您至少配置了一个外部身份验证提供程序,例如 Google。只需转到登录页面并登录即可成为管理员。 + + + 进入登录页面。 + + + OR + + + 安装 + + + 您看到此屏幕是因为尚无用户存在。创建用户后,您将无法再次使用此屏幕。 + + + 制作 + + + Sebastian Stehle 和贡献者,2016-2021 + + + 通过你的设置,只有管理员可以创建新的应用程序。如果你想改变这个设置 <code>UI__ONLYADMINCANCREATEAPPS=false</code> 作为环境变量。 + + + 通过你的设置,每个用户都可以创建新的应用程序。如果你想改变这个设置 <code>UI__ONLYADMINCANCREATEAPPS=true</code> 作为环境变量。 + + + 您正在使用<strong>文件夹资源存储</strong>,其中所有资源都存储在文件系统中。请记住将资源文件夹包含在您的备份策略中并将其映射到卷, 如果您使用的是 Docker。 + + + 您正在使用 <strong>FTP 资源存储</strong>。由于性能不佳,不建议使用这种存储类型。 + + + 您不是通过 https 访问站点。如果此警告不正确,则 Squidex 无法检测 https 模式,因为您的实例位于反向代理(例如 nginx)之后。确保正确转发 http 标头, 通过 <code>X-Forwarded-*</code> 标头。 + + + 恭喜您,您正在通过安全连接 (https) 访问 Squidex 安装。 + + + 系统清单 + + + 您应该仅通过一个规范 URL 访问 Squidex,并通过 <code>URLS__BASEURL</code> 环境变量配置此 URL。当前的基本 URL <code>{actual}</code>与基本 url <code>{configured}</code> 不匹配。 + + + 恭喜 <code>URLS__BASEURL</code> 环境变量配置正确。 + + + 安装 + + + 不允许此操作,您的帐户可能被锁定。 + + + 拒绝访问 + + + 我同意! + + + Cookies & Analytics + + + <p>我理解并同意 Squidex 使用 cookie 来确保您在我们的平台上获得最佳体验并存储您的登录状态。</p><p>我理解并同意 Squidex 已集成 Google Analytics(具有匿名器功能)。Google Analytics 是一项网络分析服务,用于收集和分析有关用户行为的数据。</p><p> 我接受 <a href="{privacyUrl}" target="_blank" rel="noopener">隐私政策</a>.</p> + + + 自动电子邮件(可选) + + + 我理解并同意 Squidex 发送电子邮件来通知我有关新功能、重大更改和停机时间的信息。 + + + 我们需要您的同意 + + + 你必须同意。 + + + 个人信息 + + + 我理解并同意 Squidex 收集从外部身份验证提供商(例如 Google、Microsoft 或 Github)检索到的以下私人信息。<ul class="personal-information"> <li>向所有其他用户提供基本的个人信息(名字、姓氏和照片),以便他们可以将您添加到他们的工作空间。</li><li>您可以随时选择更改这些信息以使您的帐户匿名. </li><li>您的用户帐户具有唯一标识符,对于我们跟踪的所有更改,您进行了这些更改并将此信息提供给其他用户。</li></ul> + + + 同意 + + + 你不能删除自己。 + + + 操作失败 + + + 我们真的很抱歉出现问题。 + + + 错误 + + + 发生意外异常。 + + + 您的账户已被锁定,请联系管理员。 + + + 帐户已锁定 + + + 你不能锁定自己。 + + + + + + 输入邮箱 + + + 电子邮件或密码不正确 + + + {action} with <strong>{provider}</strong> + + + 点击此处登录 + + + 已经注册? + + + 点击此处注册 + + + 还没有账号? + + + 输入密码 + + + + + + 登出! + + + !请关闭这个弹窗。 + + + 注销 + + + 登录添加成功。 + + + 更改密码 + + + 密码更改成功。 + + + 使用客户端凭据通过您的个人资料信息和权限访问 API + + + 客户端 + + + 确认 + + + 生成 + + + 客户端密码生成成功。 + + + 编辑个人资料 + + + 不要向其他用户显示我的个人资料 + + + 登录 + + + 密码 + + + 个人信息 + + + 为规则和脚本使用自定义属性。 + + + 属性 + + + 添加属性 + + + 登录提供程序删除成功。 + + + 设置密码 + + + 密码设置成功。 + + + 个人资料 + + + 账户更新成功。 + + + 账户更新成功。 + + + 上传图片 + + + 图片上传成功。 + + + 您无法解锁自己。 + + + 不允许用户登录。 + + + 找不到用户。 + + + {property|upper} 必须介于 {min} 和 {max} 之间。 + + + {property|upper} 必须大于或等于 {other|lower}。 + + + {property|upper} 必须大于 {other|lower}。 + + + {property|upper} 不是 Javascript 属性名称。 + + + {property|upper} 必须小于或等于 {other|lower}。 + + + {property|upper} 必须小于 {other|lower}。 + + + 图片不是有效图片。 + + + 只能上传一个文件。 + + + {property|upper} 是必需的。 + + + 如果使用 {property1|lower} 或 {property2|lower},则必须定义两者。 + + + 必须定义值。 + + + {property|upper} 不是有效的 slug。 + + + {property|upper} 不是一个有效值。 + + + 多个工作流覆盖所有Schemas。 + + + 初始步骤不能发布步骤。 + + + 工作流必须有一个已发布的步骤。 + + + 转换的目标无效。 + + + Schemas'{schema}' 被多个工作流覆盖。 + + \ No newline at end of file diff --git a/backend/src/Squidex.Web/ApiController.cs b/backend/src/Squidex.Web/ApiController.cs index d8734329b..e5244d4b8 100644 --- a/backend/src/Squidex.Web/ApiController.cs +++ b/backend/src/Squidex.Web/ApiController.cs @@ -73,8 +73,6 @@ namespace Squidex.Web protected ApiController(ICommandBus commandBus) { - Guard.NotNull(commandBus, nameof(commandBus)); - CommandBus = commandBus; resources = new Lazy(() => new Resources(this)); diff --git a/backend/src/Squidex.Web/CommandMiddlewares/ETagCommandMiddleware.cs b/backend/src/Squidex.Web/CommandMiddlewares/ETagCommandMiddleware.cs index 9a465bc49..123f26053 100644 --- a/backend/src/Squidex.Web/CommandMiddlewares/ETagCommandMiddleware.cs +++ b/backend/src/Squidex.Web/CommandMiddlewares/ETagCommandMiddleware.cs @@ -22,8 +22,6 @@ namespace Squidex.Web.CommandMiddlewares public ETagCommandMiddleware(IHttpContextAccessor httpContextAccessor) { - Guard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); - this.httpContextAccessor = httpContextAccessor; } diff --git a/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs b/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs index e9afa0abc..26e800b4f 100644 --- a/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs +++ b/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs @@ -20,8 +20,6 @@ namespace Squidex.Web.CommandMiddlewares public EnrichWithActorCommandMiddleware(IHttpContextAccessor httpContextAccessor) { - Guard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); - this.httpContextAccessor = httpContextAccessor; } diff --git a/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs b/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs index 769a0c931..090bb21e4 100644 --- a/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs +++ b/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs @@ -20,8 +20,6 @@ namespace Squidex.Web.CommandMiddlewares public EnrichWithAppIdCommandMiddleware(IContextProvider contextProvider) { - Guard.NotNull(contextProvider, nameof(contextProvider)); - this.contextProvider = contextProvider; } diff --git a/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs b/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs index e8bcbaf51..c47ffb723 100644 --- a/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs +++ b/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs @@ -21,8 +21,6 @@ namespace Squidex.Web.CommandMiddlewares public EnrichWithSchemaIdCommandMiddleware(IHttpContextAccessor httpContextAccessor) { - Guard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); - this.httpContextAccessor = httpContextAccessor; } diff --git a/backend/src/Squidex.Web/ContextProvider.cs b/backend/src/Squidex.Web/ContextProvider.cs index b2e4a9605..1ef8c51b8 100644 --- a/backend/src/Squidex.Web/ContextProvider.cs +++ b/backend/src/Squidex.Web/ContextProvider.cs @@ -8,7 +8,6 @@ using System.Threading; using Microsoft.AspNetCore.Http; using Squidex.Domain.Apps.Entities; -using Squidex.Infrastructure; namespace Squidex.Web { @@ -37,8 +36,6 @@ namespace Squidex.Web public ContextProvider(IHttpContextAccessor httpContextAccessor) { - Guard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); - this.httpContextAccessor = httpContextAccessor; } } diff --git a/backend/src/Squidex.Web/Pipeline/ActionContextLogAppender.cs b/backend/src/Squidex.Web/Pipeline/ActionContextLogAppender.cs index e37041e65..146d84b3f 100644 --- a/backend/src/Squidex.Web/Pipeline/ActionContextLogAppender.cs +++ b/backend/src/Squidex.Web/Pipeline/ActionContextLogAppender.cs @@ -9,7 +9,6 @@ using System; using System.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Squidex.Infrastructure; using Squidex.Log; namespace Squidex.Web.Pipeline @@ -21,9 +20,6 @@ namespace Squidex.Web.Pipeline public ActionContextLogAppender(IActionContextAccessor actionContextAccessor, IHttpContextAccessor httpContextAccessor) { - Guard.NotNull(actionContextAccessor, nameof(actionContextAccessor)); - Guard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); - this.actionContextAccessor = actionContextAccessor; this.httpContextAccessor = httpContextAccessor; diff --git a/backend/src/Squidex.Web/Pipeline/ApiCostsFilter.cs b/backend/src/Squidex.Web/Pipeline/ApiCostsFilter.cs index 038ebede3..0730999ef 100644 --- a/backend/src/Squidex.Web/Pipeline/ApiCostsFilter.cs +++ b/backend/src/Squidex.Web/Pipeline/ApiCostsFilter.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Squidex.Domain.Apps.Entities.Apps.Plans; -using Squidex.Infrastructure; using Squidex.Log; namespace Squidex.Web.Pipeline @@ -22,8 +21,6 @@ namespace Squidex.Web.Pipeline public ApiCostsFilter(UsageGate usageGate) { - Guard.NotNull(usageGate, nameof(usageGate)); - this.usageGate = usageGate; } diff --git a/backend/src/Squidex.Web/Pipeline/AppResolver.cs b/backend/src/Squidex.Web/Pipeline/AppResolver.cs index 5b2650449..eeecd45e6 100644 --- a/backend/src/Squidex.Web/Pipeline/AppResolver.cs +++ b/backend/src/Squidex.Web/Pipeline/AppResolver.cs @@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Infrastructure; using Squidex.Infrastructure.Security; using Squidex.Log; using Squidex.Shared; @@ -30,8 +29,6 @@ namespace Squidex.Web.Pipeline public AppResolver(IAppProvider appProvider) { - Guard.NotNull(appProvider, nameof(appProvider)); - this.appProvider = appProvider; } @@ -60,16 +57,18 @@ namespace Squidex.Web.Pipeline return; } + string? clientId = null; + var (role, permissions) = FindByOpenIdSubject(app, user, isFrontend); if (permissions == null) { - (role, permissions) = FindByOpenIdClient(app, user, isFrontend); + (clientId, role, permissions) = FindByOpenIdClient(app, user, isFrontend); } if (permissions == null) { - (role, permissions) = FindAnonymousClient(app, isFrontend); + (clientId, role, permissions) = FindAnonymousClient(app, isFrontend); } if (permissions != null) @@ -85,6 +84,11 @@ namespace Squidex.Web.Pipeline { identity.AddClaim(new Claim(SquidexClaimTypes.Permissions, permission.Id)); } + + if (user.Token() == null && clientId != null) + { + identity.AddClaim(new Claim(OpenIdClaims.ClientId, clientId)); + } } var requestContext = SetContext(context.HttpContext, app); @@ -150,45 +154,55 @@ namespace Squidex.Web.Pipeline return context.ActionDescriptor.EndpointMetadata.Any(x => x is AllowAnonymousAttribute); } - private static (string?, PermissionSet?) FindByOpenIdClient(IAppEntity app, ClaimsPrincipal user, bool isFrontend) + private static (string?, string?, PermissionSet?) FindByOpenIdClient(IAppEntity app, ClaimsPrincipal user, bool isFrontend) { var (appName, clientId) = user.GetClient(); - if (app.Name != appName) + if (app.Name != appName || clientId == null) { - return (null, null); + return default; } - if (clientId != null && app.Clients.TryGetValue(clientId, out var client) && app.Roles.TryGet(app.Name, client.Role, isFrontend, out var role)) + if (app.Clients.TryGetValue(clientId, out var client) && app.Roles.TryGet(app.Name, client.Role, isFrontend, out var role)) { - return (client.Role, role.Permissions); + return (clientId, client.Role, role.Permissions); } - return (null, null); + return default; } - private static (string?, PermissionSet?) FindAnonymousClient(IAppEntity app, bool isFrontend) + private static (string?, string?, PermissionSet?) FindAnonymousClient(IAppEntity app, bool isFrontend) { - var client = app.Clients.Values.FirstOrDefault(x => x.AllowAnonymous); + var client = app.Clients.FirstOrDefault(x => x.Value.AllowAnonymous); - if (client != null && app.Roles.TryGet(app.Name, client.Role, isFrontend, out var role)) + if (client.Value == null) { - return (client.Role, role.Permissions); + return default; } - return (null, null); + if (app.Roles.TryGet(app.Name, client.Value.Role, isFrontend, out var role)) + { + return (client.Key, client.Value.Role, role.Permissions); + } + + return default; } private static (string?, PermissionSet?) FindByOpenIdSubject(IAppEntity app, ClaimsPrincipal user, bool isFrontend) { var subjectId = user.OpenIdSubject(); - if (subjectId != null && app.Contributors.TryGetValue(subjectId, out var roleName) && app.Roles.TryGet(app.Name, roleName, isFrontend, out var role)) + if (subjectId == null) + { + return default; + } + + if (app.Contributors.TryGetValue(subjectId, out var roleName) && app.Roles.TryGet(app.Name, roleName, isFrontend, out var role)) { return (roleName, role.Permissions); } - return (null, null); + return default; } } } diff --git a/backend/src/Squidex.Web/Pipeline/CachingFilter.cs b/backend/src/Squidex.Web/Pipeline/CachingFilter.cs index 295ba28f4..29b538c37 100644 --- a/backend/src/Squidex.Web/Pipeline/CachingFilter.cs +++ b/backend/src/Squidex.Web/Pipeline/CachingFilter.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Net.Http.Headers; -using Squidex.Infrastructure; namespace Squidex.Web.Pipeline { @@ -21,8 +20,6 @@ namespace Squidex.Web.Pipeline public CachingFilter(CachingManager cachingManager) { - Guard.NotNull(cachingManager, nameof(cachingManager)); - this.cachingManager = cachingManager; } diff --git a/backend/src/Squidex.Web/Pipeline/CachingKeysMiddleware.cs b/backend/src/Squidex.Web/Pipeline/CachingKeysMiddleware.cs index 9cd742a71..fd3e083c0 100644 --- a/backend/src/Squidex.Web/Pipeline/CachingKeysMiddleware.cs +++ b/backend/src/Squidex.Web/Pipeline/CachingKeysMiddleware.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; -using Squidex.Infrastructure; using Squidex.Infrastructure.Security; namespace Squidex.Web.Pipeline @@ -23,10 +22,6 @@ namespace Squidex.Web.Pipeline public CachingKeysMiddleware(CachingManager cachingManager, IOptions cachingOptions, RequestDelegate next) { - Guard.NotNull(cachingManager, nameof(cachingManager)); - Guard.NotNull(cachingOptions, nameof(cachingOptions)); - Guard.NotNull(next, nameof(next)); - this.cachingOptions = cachingOptions.Value; this.cachingManager = cachingManager; diff --git a/backend/src/Squidex.Web/Pipeline/CachingManager.cs b/backend/src/Squidex.Web/Pipeline/CachingManager.cs index 4dc6e95ec..35e71dbfe 100644 --- a/backend/src/Squidex.Web/Pipeline/CachingManager.cs +++ b/backend/src/Squidex.Web/Pipeline/CachingManager.cs @@ -172,9 +172,6 @@ namespace Squidex.Web.Pipeline public CachingManager(IHttpContextAccessor httpContextAccessor, IOptions cachingOptions) { - Guard.NotNull(httpContextAccessor, nameof(httpContextAccessor)); - Guard.NotNull(cachingOptions, nameof(cachingOptions)); - this.httpContextAccessor = httpContextAccessor; this.cachingOptions = cachingOptions.Value; diff --git a/backend/src/Squidex.Web/Pipeline/UsageMiddleware.cs b/backend/src/Squidex.Web/Pipeline/UsageMiddleware.cs index bb391da73..42450e072 100644 --- a/backend/src/Squidex.Web/Pipeline/UsageMiddleware.cs +++ b/backend/src/Squidex.Web/Pipeline/UsageMiddleware.cs @@ -24,10 +24,6 @@ namespace Squidex.Web.Pipeline public UsageMiddleware(IAppLogStore usageLog, IApiUsageTracker usageTracker, IClock clock) { - Guard.NotNull(usageLog, nameof(usageLog)); - Guard.NotNull(usageTracker, nameof(usageTracker)); - Guard.NotNull(clock, nameof(clock)); - this.usageLog = usageLog; this.usageTracker = usageTracker; diff --git a/backend/src/Squidex.Web/Services/StringLocalizer.cs b/backend/src/Squidex.Web/Services/StringLocalizer.cs index 15a0b1362..7ce6c24bd 100644 --- a/backend/src/Squidex.Web/Services/StringLocalizer.cs +++ b/backend/src/Squidex.Web/Services/StringLocalizer.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using Microsoft.Extensions.Localization; -using Squidex.Infrastructure; using Squidex.Infrastructure.Translations; using Squidex.Text; @@ -79,8 +78,6 @@ namespace Squidex.Web.Services private StringLocalizer(ILocalizer translationService, CultureInfo? culture) { - Guard.NotNull(translationService, nameof(translationService)); - this.translationService = translationService; this.culture = culture; diff --git a/backend/src/Squidex.Web/Services/UrlGenerator.cs b/backend/src/Squidex.Web/Services/UrlGenerator.cs index c732c0327..efb6e316e 100644 --- a/backend/src/Squidex.Web/Services/UrlGenerator.cs +++ b/backend/src/Squidex.Web/Services/UrlGenerator.cs @@ -22,9 +22,6 @@ namespace Squidex.Web.Services public UrlGenerator(IGenericUrlGenerator urlGenerator, IAssetFileStore assetFileStore, bool allowAssetSourceUrl) { - Guard.NotNull(assetFileStore, nameof(assetFileStore)); - Guard.NotNull(urlGenerator, nameof(urlGenerator)); - this.assetFileStore = assetFileStore; this.urlGenerator = urlGenerator; @@ -64,7 +61,7 @@ namespace Squidex.Web.Services public string? AssetSource(NamedId appId, DomainId assetId, long fileVersion) { - return assetFileStore.GeneratePublicUrl(appId.Id, assetId, fileVersion); + return assetFileStore.GeneratePublicUrl(appId.Id, assetId, fileVersion, null); } public string AssetsUI(NamedId appId, string? query = null) diff --git a/backend/src/Squidex.Web/Squidex.Web.csproj b/backend/src/Squidex.Web/Squidex.Web.csproj index 76db03243..5ae80b254 100644 --- a/backend/src/Squidex.Web/Squidex.Web.csproj +++ b/backend/src/Squidex.Web/Squidex.Web.csproj @@ -16,7 +16,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs index fc1d9d316..08ee667b1 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs @@ -30,7 +30,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models .ToArray() }; - return result; + return result.CreateLinks(resources); } private ClientsDto CreateLinks(Resources resources) diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs index cf46e4fe1..c0e1b143a 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs @@ -36,7 +36,6 @@ namespace Squidex.Areas.Api.Controllers.Assets private readonly IAssetFileStore assetFileStore; private readonly IAssetQueryService assetQuery; private readonly IAssetLoader assetLoader; - private readonly IAssetStore assetStore; private readonly IAssetThumbnailGenerator assetThumbnailGenerator; public AssetContentController( @@ -44,14 +43,12 @@ namespace Squidex.Areas.Api.Controllers.Assets IAssetFileStore assetFileStore, IAssetQueryService assetQuery, IAssetLoader assetLoader, - IAssetStore assetStore, IAssetThumbnailGenerator assetThumbnailGenerator) : base(commandBus) { this.assetFileStore = assetFileStore; this.assetQuery = assetQuery; this.assetLoader = assetLoader; - this.assetStore = assetStore; this.assetThumbnailGenerator = assetThumbnailGenerator; } @@ -162,21 +159,21 @@ namespace Squidex.Areas.Api.Controllers.Assets { callback = async (bodyStream, range, ct) => { - var resizedAsset = $"{asset.AppId.Id}_{asset.Id}_{asset.FileVersion}_{resizeOptions}"; - if (request.ForceResize) { - await ResizeAsync(asset, bodyStream, resizedAsset, resizeOptions, true, ct); + await ResizeAsync(asset, bodyStream, resizeOptions, true, ct); } else { try { - await assetStore.DownloadAsync(resizedAsset, bodyStream, ct: ct); + var suffix = resizeOptions.ToString(); + + await assetFileStore.DownloadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, suffix, bodyStream, ct: ct); } catch (AssetNotFoundException) { - await ResizeAsync(asset, bodyStream, resizedAsset, resizeOptions, false, ct); + await ResizeAsync(asset, bodyStream, resizeOptions, false, ct); } } }; @@ -187,7 +184,7 @@ namespace Squidex.Areas.Api.Controllers.Assets callback = async (bodyStream, range, ct) => { - await assetFileStore.DownloadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, bodyStream, range, ct); + await assetFileStore.DownloadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, null, bodyStream, range, ct); }; } @@ -202,8 +199,10 @@ namespace Squidex.Areas.Api.Controllers.Assets }; } - private async Task ResizeAsync(IAssetEntity asset, Stream bodyStream, string fileName, ResizeOptions resizeOptions, bool overwrite, CancellationToken ct) + private async Task ResizeAsync(IAssetEntity asset, Stream bodyStream, ResizeOptions resizeOptions, bool overwrite, CancellationToken ct) { + var suffix = resizeOptions.ToString(); + using (Profiler.Trace("Resize")) { using (var sourceStream = GetTempStream()) @@ -212,7 +211,7 @@ namespace Squidex.Areas.Api.Controllers.Assets { using (Profiler.Trace("ResizeDownload")) { - await assetFileStore.DownloadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, sourceStream); + await assetFileStore.DownloadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, null, sourceStream); sourceStream.Position = 0; } @@ -234,7 +233,7 @@ namespace Squidex.Areas.Api.Controllers.Assets { using (Profiler.Trace("ResizeUpload")) { - await assetStore.UploadAsync(fileName, destinationStream, overwrite); + await assetFileStore.UploadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, suffix, destinationStream, overwrite); destinationStream.Position = 0; } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/News/Service/FeaturesService.cs b/backend/src/Squidex/Areas/Api/Controllers/News/Service/FeaturesService.cs index 73e1fcec3..222aeb988 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/News/Service/FeaturesService.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/News/Service/FeaturesService.cs @@ -16,7 +16,7 @@ namespace Squidex.Areas.Api.Controllers.News.Service { public sealed class FeaturesService { - private const int FeatureVersion = 17; + private const int FeatureVersion = 18; private readonly QueryContext flatten = QueryContext.Default.Flatten(); private readonly IContentsClient client; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs index 53ec76ecb..9e5fcafff 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs @@ -14,7 +14,6 @@ using NSwag.Generation.Processors.Contexts; using Squidex.Domain.Apps.Core.GenerateJsonSchema; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Infrastructure; namespace Squidex.Areas.Api.Controllers.Rules.Models { @@ -24,8 +23,6 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models public RuleActionProcessor(RuleRegistry ruleRegistry) { - Guard.NotNull(ruleRegistry, nameof(ruleRegistry)); - this.ruleRegistry = ruleRegistry; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs index 582befc74..d44e2301b 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs @@ -33,6 +33,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// public Instant? MinValue { get; set; } + /// + /// The format pattern when displayed in the UI. + /// + public string? Format { get; set; } + /// /// The editor that is used to manage this field. /// diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaDto.cs index 5ffa8078a..b93e04191 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaDto.cs @@ -45,6 +45,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models /// public ImmutableDictionary? PreviewUrls { get; set; } + /// + /// The optional field Rules. + /// + public List? FieldRules { get; set; } + /// /// The category. /// @@ -113,6 +118,18 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models command.Fields = fields.ToArray(); } + if (dto.FieldRules?.Count > 0) + { + var fieldRuleCommands = new List(); + + foreach (var fieldRule in dto.FieldRules) + { + fieldRuleCommands.Add(fieldRule.ToCommand()); + } + + command.FieldRules = fieldRuleCommands.ToArray(); + } + return command; } } diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/AlwaysAddTokenHandler.cs b/backend/src/Squidex/Areas/IdentityServer/Config/AlwaysAddTokenHandler.cs new file mode 100644 index 000000000..6a8cf853d --- /dev/null +++ b/backend/src/Squidex/Areas/IdentityServer/Config/AlwaysAddTokenHandler.cs @@ -0,0 +1,35 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Immutable; +using System.Threading.Tasks; +using OpenIddict.Abstractions; +using OpenIddict.Server; +using static OpenIddict.Server.OpenIddictServerEvents; + +namespace Squidex.Areas.IdentityServer.Config +{ + public sealed class AlwaysAddTokenHandler : IOpenIddictServerHandler + { + public ValueTask HandleAsync(ProcessSignInContext context) + { + if (context == null) + { + return default; + } + + if (!string.IsNullOrWhiteSpace(context.Response.AccessToken)) + { + var scopes = context.AccessTokenPrincipal?.GetScopes() ?? ImmutableArray.Empty; + + context.Response.Scope = string.Join(" ", scopes); + } + + return default; + } + } +} diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/ApplicationExtensions.cs b/backend/src/Squidex/Areas/IdentityServer/Config/ApplicationExtensions.cs index 61c504369..80016cae9 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Config/ApplicationExtensions.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Config/ApplicationExtensions.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Security.Claims; using System.Text.Json; using OpenIddict.Abstractions; +using Squidex.Infrastructure.Security; using Squidex.Shared; using Squidex.Shared.Identity; using Squidex.Shared.Users; @@ -50,6 +51,11 @@ namespace Squidex.Areas.IdentityServer.Config { foreach (var claimValue in values) { + if (key == SquidexClaimTypes.DisplayName) + { + yield return new Claim(OpenIdClaims.Name, claimValue); + } + yield return new Claim(key, claimValue); } } diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/DynamicApplicationStore.cs b/backend/src/Squidex/Areas/IdentityServer/Config/DynamicApplicationStore.cs index 0f71b8283..ccb1ba2f5 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Config/DynamicApplicationStore.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Config/DynamicApplicationStore.cs @@ -33,8 +33,6 @@ namespace Squidex.Areas.IdentityServer.Config public DynamicApplicationStore(IServiceProvider serviceProvider) : base(CreateStaticClients(serviceProvider)) { - Guard.NotNull(serviceProvider, nameof(serviceProvider)); - this.serviceProvider = serviceProvider; } diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs b/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs index 09dece9a4..f3c1b861d 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs @@ -20,6 +20,8 @@ using Squidex.Hosting; using Squidex.Web; using Squidex.Web.Pipeline; using static OpenIddict.Abstractions.OpenIddictConstants; +using static OpenIddict.Server.OpenIddictServerEvents; +using static OpenIddict.Server.OpenIddictServerHandlers; namespace Squidex.Areas.IdentityServer.Config { @@ -41,9 +43,6 @@ namespace Squidex.Areas.IdentityServer.Config services.AddSingletonAs() .As(); - services.AddSingletonAs() - .As>(); - services.AddScopedAs() .As(); @@ -53,6 +52,9 @@ namespace Squidex.Areas.IdentityServer.Config services.AddSingletonAs() .As(); + services.AddSingletonAs() + .AsSelf(); + services.AddSingletonAs() .AsSelf(); @@ -78,6 +80,12 @@ namespace Squidex.Areas.IdentityServer.Config }) .AddServer(builder => { + builder.AddEventHandler(builder => + { + builder.UseSingletonHandler() + .SetOrder(AttachTokenParameters.Descriptor.Order + 1); + }); + builder .SetAuthorizationEndpointUris("/connect/authorize") .SetIntrospectionEndpointUris("/connect/introspect") @@ -101,7 +109,6 @@ namespace Squidex.Areas.IdentityServer.Config builder.AllowAuthorizationCodeFlow(); builder.UseAspNetCore() - // Disable it mainly for our tests. .DisableTransportSecurityRequirement() .EnableAuthorizationEndpointPassthrough() .EnableLogoutEndpointPassthrough() diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/TokenStoreInitializer.cs b/backend/src/Squidex/Areas/IdentityServer/Config/TokenStoreInitializer.cs new file mode 100644 index 000000000..03681d100 --- /dev/null +++ b/backend/src/Squidex/Areas/IdentityServer/Config/TokenStoreInitializer.cs @@ -0,0 +1,82 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using MongoDB.Driver; +using OpenIddict.Abstractions; +using OpenIddict.MongoDb; +using OpenIddict.MongoDb.Models; +using Squidex.Hosting; +using Squidex.Infrastructure.Timers; + +namespace Squidex.Areas.IdentityServer.Config +{ + public sealed class TokenStoreInitializer : IInitializable + { + private readonly OpenIddictMongoDbOptions options; + private readonly IServiceProvider serviceProvider; + private CompletionTimer timer; + + public TokenStoreInitializer(IOptions options, + IServiceProvider serviceProvider) + { + this.options = options.Value; + + this.serviceProvider = serviceProvider; + } + + public async Task InitializeAsync(CancellationToken ct) + { + await SetupIndexAsync(ct); + + await PruneAsync(ct); + + timer = new CompletionTimer((int)TimeSpan.FromHours(6).TotalMilliseconds, async ct => + { + await PruneAsync(ct); + }); + } + + private async Task PruneAsync(CancellationToken ct) + { + using (var scope = serviceProvider.CreateScope()) + { + var tokenManager = scope.ServiceProvider.GetRequiredService(); + + await tokenManager.PruneAsync(DateTimeOffset.UtcNow.AddDays(-40), ct); + } + } + + private async Task SetupIndexAsync(CancellationToken ct) + { + using (var scope = serviceProvider.CreateScope()) + { + var database = await scope.ServiceProvider.GetRequiredService().GetDatabaseAsync(ct); + + var collection = database.GetCollection>(options.TokensCollectionName); + + await collection.Indexes.CreateOneAsync( + new CreateIndexModel>( + Builders>.IndexKeys + .Ascending(x => x.ReferenceId)), + cancellationToken: ct); + } + } + + public async Task ReleaseAsync(CancellationToken ct) + { + if (timer != null) + { + await timer.StopAsync(); + } + } + } +} diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Connect/ConnectController.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Connect/ConnectController.cs index ecb126e97..891a2bc49 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Connect/ConnectController.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Connect/ConnectController.cs @@ -184,19 +184,11 @@ namespace Notifo.Areas.Account.Controllers var principal = new ClaimsPrincipal(identity); - var clientId = request.ClientId; - var clientName = await applicationManager.GetDisplayNameAsync(application); - - if (clientId != null) + if (request.ClientId != null) { - identity.AddClaim(Claims.Subject, clientId, - Destinations.AccessToken, Destinations.IdentityToken); - } - - if (clientName != null) - { - identity.AddClaim(Claims.Name, clientName, - Destinations.AccessToken, Destinations.IdentityToken); + identity.AddClaim(Claims.Subject, request.ClientId, + Destinations.AccessToken, + Destinations.IdentityToken); } var properties = await applicationManager.GetPropertiesAsync(application); @@ -228,7 +220,7 @@ namespace Notifo.Areas.Account.Controllers { switch (claim.Type) { - case SquidexClaimTypes.DisplayName when principal.HasScope(Scopes.Profile): + case SquidexClaimTypes.DisplayName: yield return Destinations.IdentityToken; yield break; @@ -274,13 +266,6 @@ namespace Notifo.Areas.Account.Controllers } yield break; - - case "AspNet.Identity.SecurityStamp": - yield break; - - default: - yield return Destinations.AccessToken; - yield break; } } } diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Error/ErrorController.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Error/ErrorController.cs index 0a5e85fb7..fa83bd8a6 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Error/ErrorController.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Error/ErrorController.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Mvc; +using Squidex.Infrastructure; namespace Squidex.Areas.IdentityServer.Controllers.Error { @@ -30,7 +31,14 @@ namespace Squidex.Areas.IdentityServer.Controllers.Error { var exception = HttpContext.Features.Get()?.Error; - vm.ErrorMessage = exception?.Message; + if (exception is DomainException domainException1) + { + vm.ErrorMessage = domainException1.Message; + } + else if (exception?.InnerException is DomainException domainException2) + { + vm.ErrorMessage = domainException2.Message; + } } return View("Error", vm); diff --git a/backend/src/Squidex/Areas/IdentityServer/Views/Account/Consent.cshtml b/backend/src/Squidex/Areas/IdentityServer/Views/Account/Consent.cshtml index 563d65e59..6ebd6eee1 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Views/Account/Consent.cshtml +++ b/backend/src/Squidex/Areas/IdentityServer/Views/Account/Consent.cshtml @@ -15,7 +15,7 @@

@T.Get("users.consent.headline")