diff --git a/.github/workflows/e2e-tests-xray_frontend.yml b/.github/workflows/e2e-tests-xray_frontend.yml index f86969bc58..885f73cdd0 100644 --- a/.github/workflows/e2e-tests-xray_frontend.yml +++ b/.github/workflows/e2e-tests-xray_frontend.yml @@ -102,7 +102,7 @@ jobs: node-version: 20.x - name: Cypress run all tests - uses: cypress-io/github-action@v6.6.1 # use the explicit version number + uses: cypress-io/github-action@v6.7.0 # use the explicit version number with: start: npm run start:auth:e2ea wait-on: "http://localhost:4200" @@ -165,7 +165,7 @@ jobs: # node-version: 18.x # # - name: Cypress run all tests - # uses: cypress-io/github-action@v6.6.1 # use the explicit version number + # uses: cypress-io/github-action@v6.7.0 # use the explicit version number # with: # start: npm start # wait-on: "http://localhost:4200" @@ -228,7 +228,7 @@ jobs: # run: npx playwright install --with-deps webkit # # - name: Cypress run all tests - # uses: cypress-io/github-action@v6.6.1 # use the explicit version number + # uses: cypress-io/github-action@v6.7.0 # use the explicit version number # with: # start: npm start:auth:e2ea # wait-on: "http://localhost:4200" diff --git a/.github/workflows/jira-publish-release.yaml b/.github/workflows/jira-publish-release.yaml deleted file mode 100644 index 39ce1b68eb..0000000000 --- a/.github/workflows/jira-publish-release.yaml +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (c) 2023 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0. -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -name: "[BE][FE][RELEASE] Jira release publishing" - -on: - workflow_dispatch: # Trigger manually - inputs: - version: - required: true - type: string - description: Version that will be released in Jira, eg. 2.0.0 - workflow_call: - inputs: - version: - required: true - type: string - description: Version that will be released in Jira, eg. 2.0.0 -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set current date as env variable - run: echo "NOW=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - - name: Echo current date - run: echo $NOW - - - name: Get Version Id Of Release - env: - JIRA_USERNAME: ${{ secrets.RELEASE_IRS_JIRA_USERNAME }} - JIRA_PASSWORD: ${{ secrets.RELEASE_IRS_JIRA_PASSWORD }} - run: | - echo "versionId=$(curl --request GET --url 'https://jira.catena-x.net/rest/api/latest/project/10211/versions' \ - --user $JIRA_USERNAME:$JIRA_PASSWORD --header 'Accept: application/json' | jq -r '.[] | select(.name == "NEXT_RELEASE").id')" >> $GITHUB_ENV - - name: Echo versionId to release - run: echo Exported $versionId as version id to release - - - name: Release Version in Jira - env: - JIRA_USERNAME: ${{ secrets.RELEASE_IRS_JIRA_USERNAME }} - JIRA_PASSWORD: ${{ secrets.RELEASE_IRS_JIRA_PASSWORD }} - VERSION: ${{ github.event.inputs.version }} - VERSION_ID: ${{ env.versionId }} - RELEASE_DATE: ${{ env.NOW }} - run: | - curl --request PUT --url "https://jira.catena-x.net/rest/api/latest/version/$VERSION_ID" \ - --user $JIRA_USERNAME:$JIRA_PASSWORD \ - --header 'Accept: application/json' \ - --header 'Content-Type: application/json' \ - --data "{ - \"archived\": false, - \"description\": \"$VERSION\", - \"id\": $VERSION_ID, - \"name\": \"$VERSION\", - \"releaseDate\": \"$RELEASE_DATE\", - \"released\": true, - \"overdue\": false, - \"projectId\": 10211 - }" - - - name: Create NEXT_RELEASE Version in Jira - env: - JIRA_USERNAME: ${{ secrets.RELEASE_IRS_JIRA_USERNAME }} - JIRA_PASSWORD: ${{ secrets.RELEASE_IRS_JIRA_PASSWORD }} - run: | - curl --request POST --url 'https://jira.catena-x.net/rest/api/latest/version' \ - --user $JIRA_USERNAME:$JIRA_PASSWORD \ - --header 'Accept: application/json' \ - --header 'Content-Type: application/json' \ - --data '{ - "archived": false, - "name": "NEXT_RELEASE", - "projectId": 10211, - "released": false - }' diff --git a/.github/workflows/pull-request_backend.yml b/.github/workflows/pull-request_backend.yml index f340361a75..314c714f96 100644 --- a/.github/workflows/pull-request_backend.yml +++ b/.github/workflows/pull-request_backend.yml @@ -25,6 +25,7 @@ env: GHCR_REGISTRY: ghcr.io JAVA_VERSION: 17 DOCKER_HUB_REGISTRY_NAMESPACE: tractusx + BACKEND_IMAGE_DOCKER_HUB: traceability-foss jobs: Check-Changelog-update: @@ -114,16 +115,16 @@ jobs: key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - - name: Verify Sonar Scan - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN_BACKEND }} - SONAR_ORGANIZATION: ${{ vars.SONAR_ORGANIZATION }} - SONAR_PROJECT_KEY: ${{ vars.SONAR_PROJECT_KEY_BACKEND }} - run: mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn --batch-mode sonar:sonar -Dsonar.coverage.jacoco.xmlReportPaths=/home/runner/work/tx-traceability-foss/tx-traceability-foss/tx-coverage/target/site/jacoco-aggregate/jacoco.xml -Dsonar.projectKey=${{ vars.SONAR_PROJECT_KEY_BACKEND }} -Dsonar.organization=${{ vars.SONAR_ORGANIZATION }} + # - name: Verify Sonar Scan + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN_BACKEND }} + # SONAR_ORGANIZATION: ${{ vars.SONAR_ORGANIZATION }} + # SONAR_PROJECT_KEY: ${{ vars.SONAR_PROJECT_KEY_BACKEND }} + # run: mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn --batch-mode sonar:sonar -Dsonar.coverage.jacoco.xmlReportPaths=/home/runner/work/traceability-foss/tx-traceability-foss/tx-coverage/target/site/jacoco-aggregate/jacoco.xml -Dsonar.projectKey=${{ vars.SONAR_PROJECT_KEY_BACKEND }} -Dsonar.organization=${{ vars.SONAR_ORGANIZATION }} Publish-docker-image: - needs: [ "Test-and-Sonar" ] + # needs: [ "Test-and-Sonar" ] runs-on: ubuntu-latest defaults: run: @@ -142,8 +143,10 @@ jobs: distribution: 'temurin' cache: 'maven' - - name: Login to GHCR Registry + env: + DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USER }} + if: env.DOCKER_HUB_USER == '' uses: docker/login-action@v3 with: registry: ${{ env.GHCR_REGISTRY }} @@ -151,9 +154,30 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build & Push docker image for GHCR ${{ env.GHCR_REGISTRY }}/${{ github.repository }}:${{ github.event.pull_request.head.sha }} + env: + DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USER }} + if: env.DOCKER_HUB_USER == '' uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ env.GHCR_REGISTRY }}/${{ github.repository }}:${{ github.event.pull_request.head.sha }} + - name: Login to Docker Hub + env: + DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USER }} + if: env.DOCKER_HUB_USER != '' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Build & push docker image for Docker Hub ${{ env.DOCKER_HUB_REGISTRY_NAMESPACE }}/${{ env.BACKEND_IMAGE_DOCKER_HUB }}:${{ github.event.pull_request.head.sha }} + env: + DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USER }} + if: env.DOCKER_HUB_USER != '' + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ env.DOCKER_HUB_REGISTRY_NAMESPACE }}/${{ env.BACKEND_IMAGE_DOCKER_HUB }}:${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f58afde5f8..644533953b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -91,14 +91,6 @@ jobs: with: body: ${{ env.CHANGELOG }} - trigger-jira: - needs: - - release - uses: ./.github/workflows/jira-publish-release.yaml - with: - version: ${{ github.ref_name }} - secrets: inherit - publish-to-swaggerhub: name: "Publish OpenAPI spec to Swaggerhub" permissions: diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 3b6806f1c4..0fc992b9eb 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -72,7 +72,7 @@ jobs: run: docker build -t localhost:5000/traceability-foss:fe_${{ github.sha }} -f ./frontend/Dockerfile . - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@0.20.0 + uses: aquasecurity/trivy-action@0.22.0 with: trivyignores: "./.github/workflows/.trivyignore" image-ref: 'localhost:5000/traceability-foss:fe_${{ github.sha }}' @@ -132,7 +132,7 @@ jobs: ref: ${{needs.prepare-env.outputs.check_sha}} - name: Run Trivy vulnerability scanner in repo mode - uses: aquasecurity/trivy-action@0.20.0 + uses: aquasecurity/trivy-action@0.22.0 with: trivyignores: "./.github/workflows/.trivyignore" scan-type: "config" @@ -178,7 +178,7 @@ jobs: tags: localhost:5000/traceability-foss:trivy - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@0.20.0 + uses: aquasecurity/trivy-action@0.22.0 with: image-ref: localhost:5000/traceability-foss:trivy trivyignores: "./.github/workflows/.trivyignore" diff --git a/CHANGELOG.md b/CHANGELOG.md index 94e4a7e604..f04cfa5e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,16 +7,37 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). _**For better traceability add the corresponding GitHub issue number in each changelog entry, please.**_ ## [UNRELEASED - DD.MM.YYYY] +### Changed +- #965 Implement proxy functionality of the IRS policy store + +### Added +- #832 added policymanagement list view, creator and editor +- #737 Added concept: Contract table -> parts link action +- XXX Added interceptor to EdcRestTemplates to log requests ### Removed - XXX Removed EdcNotifiactionMockServiceImpl class and replaced with mocks +- #1033 removed action jira-publish-release workflow + +### Changed + +- XXX updated JsonSchemaTest now the test pulls the latest version of the json file + +### Added + +- #1017 added file for CC BY 4.0 license for TRG 7 + +### Changed + +- #1017 updated contributing, notice, and readme files for TRG 7 ## [11.0.2 - 29.05.2024] ### Added - #1010 Made submodel path configurable - #838 Added User experience > Table design section in arc42 documentation + ### Changed - #1010 Updated IRS Helm Version from 5.1.6 to 5.1.7 diff --git a/COMPATIBILITY_MATRIX.md b/COMPATIBILITY_MATRIX.md index a02183273c..dcd68ecaff 100644 --- a/COMPATIBILITY_MATRIX.md +++ b/COMPATIBILITY_MATRIX.md @@ -1,5 +1,5 @@ # Compatibility matrix Trace-X -## Trace-X version [[11.0.2](https://github.com/eclipse-tractusx/traceability-foss/releases/tag/11.0.2] - 2024-05-29 +## Trace-X version [11.0.2](https://github.com/eclipse-tractusx/traceability-foss/releases/tag/11.0.2) - 2024-05-29 ### Catena-X Release? @@ -27,7 +27,7 @@ | Aspect Model | SingleLevelBomAsPlanned | 3.0.0 | - | -## Trace-X version [[11.0.1](https://github.com/eclipse-tractusx/traceability-foss/releases/tag/11.0.1] - 2024-05-22 +## Trace-X version [11.0.1](https://github.com/eclipse-tractusx/traceability-foss/releases/tag/11.0.1) - 2024-05-22 ### Catena-X Release? diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 119b7cd8cb..ae2dc59577 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,13 +4,15 @@ Thanks for your interest in this project. # Table of Contents 1. [Project description](#project_description) -2. [Developer resources](#developer_ressources) -3. [Problem Eclipse Development Process](#eclipse_commitment) -4. [Eclipse Contributor Agreement](#eclipse_agreement) -5. [General contribution to the project](#general) -6. [Contributing as a Consultant](#consultant) -7. [Contributing as a Developer](#developer) -8. [Contact](#contact) +2. [Project licenses](#project_licenses) +3. [Terms of Use](#terms_of_use) +4. [Developer resources](#developer_ressources) +5. [Problem Eclipse Development Process](#eclipse_commitment) +6. [Eclipse Contributor Agreement](#eclipse_agreement) +7. [General contribution to the project](#general) +8. [Contributing as a Consultant](#consultant) +9. [Contributing as a Developer](#developer) +10. [Contact](#contact) ## Project description @@ -28,6 +30,19 @@ Catena-X alliance focusing on parts traceability. - https://projects.eclipse.org/projects/automotive.tractusx - https://github.com/eclipse-tractusx/traceability-foss +## Project licenses + +The Tractus-X project uses the following licenses: + +* Apache-2.0 for code +* CC-BY-4.0 for non-code + +## Terms of Use + +This repository is subject to the Terms of Use of the Eclipse Foundation + +* https://www.eclipse.org/legal/termsofuse.php + ## Developer resources Information regarding source code management, builds, coding standards, and @@ -35,6 +50,10 @@ more. - https://projects.eclipse.org/projects/automotive.tractusx/developer +Getting started: + +* https://eclipse-tractusx.github.io/docs/developer + The project maintains the source code repositories in the following GitHub organization: - https://github.com/eclipse-tractusx/ @@ -65,14 +84,62 @@ https://www.eclipse.org/projects/handbook/#resources-commit ## General contribution to the project -### Maintaining [CHANGELOG.md](CHANGELOG.md) +### Maintaining [CHANGELOG.md](CHANGELOG.md) 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). +to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +* Add release notes for new version in [CHANGELOG.md](CHANGELOG.md) +* Features of your product(s) or component(s), available for market entry +* Please provide minimum 5 to maximum 10 key feature descriptions +* Use category “ADDED” as per good practice +* Known knowns as of the baseline of our recent Gate4 reviews (for example these can be unresolved bugs (also medium ones) or SEC weaknesses etc.) +* Put yourself into the position of an “external” customer/user of your software and focus on what is worth mentioning (if anything) +* Create a category „KNOWN KNOWNS“ if applicable +* Make sure, your [CHANGELOG.md](CHANGELOG.md) fully correlates to your market-entry version _**For better traceability add the corresponding GitHub issue number in each changelog entry, please.**_ +### git-hooks +Use git-hooks to ensure commit message consistency. +Detailed pattern can be found here: [commit-msg](https://github.com/eclipse-tractusx/traceability-foss/blob/457cb3523e981ef6aed98355a7faf0ff29867c33/dev/commit-msg#L4) + +#### How to use + +```` +cp dev/commit-msg .git/hooks/commit-msg && chmod 500 .git/hooks/commit-msg +```` +For further information, please see https://github.com/hazcod/semantic-commit-hook + +**Good practices** + +The commit messages have to match a pattern in the form of: + +```` +< type >(optional scope):[] < description > +```` + +```` +fix(api):[TRACEFOSS-123] Fix summary what is fixed. +chore(repos):[TRACEFOSS-123] Configuration change of ci cd pipeline for new repository. +docs(arc42):[TRACEFOSS-123] Added level 1 description for runtime view. + +chore(helm): TRACEFOSS-1131- Moving the values under the global key - increasing the version +```` + +## Pull Request &  Reviews + +The goal is that the maximal life cycle of a pull request: 1.5 days. + +**Steps:** + +- Every developer creating a pull request is responsible to assign a reviewer. +- Add comment for pull request with required information.  +- Please check the availability of a reviewer. The daily might be a good way to check the availability of team members. +- If Review needs to be planned: Assign corresponding Jira ticket to reviewer, with link to pull-request + ### Dash IP Prerequisites: 1) Create access token @@ -87,6 +154,15 @@ You start off with the main branch, then a developer creates a feature branch di After the feature is developed the code is reviewed and tested on the branch. Only after the code is stable it can be merged to main. +- Main dev work is done on feature branches + - Branches must be prefixed according to their nature: + - feature/* - for implementing user stories + - fix/* - for fixing bugs that appeared in the main branch + - chore/* - any small change without any impact +- Branch Name: + - MUST contain : Issue ID in the format #XXX + - MUST contain: Subject of issue (Abbreviation of pbi summary without using spaces / use "-" to connect) + ### Commit messages diff --git a/DEPENDENCIES_BACKEND b/DEPENDENCIES_BACKEND index 4fd7cbe35e..6554053f34 100644 --- a/DEPENDENCIES_BACKEND +++ b/DEPENDENCIES_BACKEND @@ -37,8 +37,11 @@ maven/mavencentral/com.google.code.gson/gson/2.10.1, Apache-2.0, approved, #6159 maven/mavencentral/com.google.code.javaparser/javaparser/1.0.11, LGPL-3.0-or-later, approved, #13474 maven/mavencentral/com.google.crypto.tink/tink/1.12.0, Apache-2.0, approved, #12041 maven/mavencentral/com.google.errorprone/error_prone_annotations/2.18.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.google.errorprone/error_prone_annotations/2.22.0, Apache-2.0, approved, #10661 maven/mavencentral/com.google.guava/failureaccess/1.0.1, Apache-2.0, approved, CQ22654 +maven/mavencentral/com.google.guava/failureaccess/1.0.2, Apache-2.0, approved, CQ22654 maven/mavencentral/com.google.guava/guava/32.0.1-jre, Apache-2.0 AND CC0-1.0 AND CC-PDDC, approved, #8772 +maven/mavencentral/com.google.guava/guava/33.0.0-jre, Apache-2.0 AND CC0-1.0, approved, #12173 maven/mavencentral/com.google.guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava, Apache-2.0, approved, CQ22657 maven/mavencentral/com.google.j2objc/j2objc-annotations/2.8, Apache-2.0, approved, clearlydefined maven/mavencentral/com.google.protobuf/protobuf-java/3.24.3, BSD-3-Clause, approved, clearlydefined @@ -66,6 +69,7 @@ maven/mavencentral/commons-codec/commons-codec/1.16.1, Apache-2.0 AND (Apache-2. maven/mavencentral/commons-collections/commons-collections/3.2.2, Apache-2.0, approved, CQ10385 maven/mavencentral/commons-digester/commons-digester/2.1, Apache-2.0, approved, clearlydefined maven/mavencentral/commons-io/commons-io/2.11.0, Apache-2.0, approved, CQ23745 +maven/mavencentral/commons-io/commons-io/2.15.1, Apache-2.0, approved, #11244 maven/mavencentral/commons-lang/commons-lang/2.6, Apache-2.0, approved, CQ6183 maven/mavencentral/commons-logging/commons-logging/1.2, Apache-2.0, approved, CQ10162 maven/mavencentral/commons-logging/commons-logging/1.3.0, Apache-2.0, approved, #11783 @@ -129,7 +133,6 @@ maven/mavencentral/io.rest-assured/xml-path/5.3.2, Apache-2.0, approved, #9267 maven/mavencentral/io.rest-assured/xml-path/5.4.0, Apache-2.0, approved, #12038 maven/mavencentral/io.smallrye/jandex/3.1.2, Apache-2.0, approved, clearlydefined maven/mavencentral/io.swagger.core.v3/swagger-annotations-jakarta/2.2.20, Apache-2.0, approved, #5947 -maven/mavencentral/io.swagger.core.v3/swagger-annotations-jakarta/2.2.8, Apache-2.0, approved, #5947 maven/mavencentral/io.swagger.core.v3/swagger-annotations/2.2.18, Apache-2.0, approved, #11362 maven/mavencentral/io.swagger.core.v3/swagger-core-jakarta/2.2.8, Apache-2.0, approved, #5929 maven/mavencentral/io.swagger.core.v3/swagger-models-jakarta/2.2.8, Apache-2.0, approved, #5919 @@ -166,6 +169,7 @@ maven/mavencentral/net.minidev/json-smart/2.5.1, Apache-2.0, approved, clearlyde maven/mavencentral/net.sf.jopt-simple/jopt-simple/5.0.4, MIT, approved, CQ13174 maven/mavencentral/org.antlr/antlr4-runtime/4.13.0, BSD-3-Clause, approved, #10767 maven/mavencentral/org.apache.commons/commons-collections4/4.4, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.apache.commons/commons-compress/1.26.0, Apache-2.0 AND (Apache-2.0 AND BSD-3-Clause), approved, #13288 maven/mavencentral/org.apache.commons/commons-compress/1.26.1, Apache-2.0 AND (Apache-2.0 AND BSD-3-Clause), approved, #13288 maven/mavencentral/org.apache.commons/commons-lang3/3.11, Apache-2.0, approved, CQ22642 maven/mavencentral/org.apache.commons/commons-lang3/3.12.0, Apache-2.0, approved, clearlydefined @@ -195,6 +199,7 @@ maven/mavencentral/org.bitbucket.b_c/jose4j/0.9.4, Apache-2.0, approved, #4707 maven/mavencentral/org.bouncycastle/bcprov-jdk18on/1.78, MIT AND CC0-1.0, approved, #14433 maven/mavencentral/org.ccil.cowan.tagsoup/tagsoup/1.2.1, Apache-2.0, approved, clearlydefined maven/mavencentral/org.checkerframework/checker-qual/3.31.0, MIT, approved, clearlydefined +maven/mavencentral/org.checkerframework/checker-qual/3.41.0, MIT, approved, #12032 maven/mavencentral/org.eclipse.angus/angus-activation/2.0.0, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.angus maven/mavencentral/org.eclipse.angus/angus-activation/2.0.2, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.angus maven/mavencentral/org.eclipse.edc/api-core/0.6.0, Apache-2.0, approved, technology.edc @@ -370,7 +375,7 @@ maven/mavencentral/org.springframework/spring-orm/6.1.6, Apache-2.0, approved, # maven/mavencentral/org.springframework/spring-test/6.1.6, Apache-2.0, approved, #12919 maven/mavencentral/org.springframework/spring-tx/6.1.6, Apache-2.0, approved, #11901 maven/mavencentral/org.springframework/spring-web/6.1.6, Apache-2.0, approved, #11748 -maven/mavencentral/org.springframework/spring-webmvc/6.1.6, Apache-2.0, approved, #11879 +maven/mavencentral/org.springframework/spring-webmvc/6.1.6, Apache-2.0, approved, #15182 maven/mavencentral/org.testcontainers/database-commons/1.19.7, Apache-2.0, approved, #10345 maven/mavencentral/org.testcontainers/jdbc/1.19.7, Apache-2.0, approved, #10348 maven/mavencentral/org.testcontainers/junit-jupiter/1.19.8, MIT, approved, #10344 diff --git a/LICENSE_non-code b/LICENSE_non-code new file mode 100644 index 0000000000..8e4d8772cf --- /dev/null +++ b/LICENSE_non-code @@ -0,0 +1,395 @@ +Creative Commons Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/NOTICE.md b/NOTICE.md index b9b139f45b..37ea8ca8af 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -18,12 +18,19 @@ source code repository logs. ## Declared Project Licenses -This program and the accompanying materials are made available under the terms -of the Apache License, Version 2.0 which is available at -https://www.apache.org/licenses/LICENSE-2.0. +The Tractus-X project uses the following licenses: +- Apache-2.0 for code +- CC-BY-4.0 for non-code + +Apache-2.0: +This program and the accompanying materials are made available under the terms of the Apache License, Version 2.0 which is available at https://www.apache.org/licenses/LICENSE-2.0. SPDX-License-Identifier: Apache-2.0 +CC-BY-4.0: +The materials in this repository are made available under the terms of the Creative Commons Attribution 4.0 International License, which is available at https://spdx.org/licenses/CC-BY-4.0.html. +SPDX-License-Identifier: CC-BY-4.0 + ## Source Code The project maintains the following source code repositories diff --git a/README.md b/README.md index 05dde42a60..261e31ff7f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@

Trace-X - Product Traceability FOSS Frontend (TRACE-FOSS)Traceability FOSS

-[![Apache 2 License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://github.com/eclipse-tractusx/traceability-foss/LICENSE) +[![Apache 2 License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://github.com/eclipse-tractusx/traceability-foss/blob/main/LICENSE) +[![CC BY 4.0 License](https://img.shields.io/badge/Non--code_license-CC%20BY%204.0-orange.svg)](https://github.com/eclipse-tractusx/traceability-foss/blob/main/LICENSE_non-code) [![QG Backend](https://sonarcloud.io/api/project_badges/measure?project=eclipse-tractusx_traceability-foss-backend&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=eclipse-tractusx_traceability-foss-backend) [![QG Frontend](https://sonarcloud.io/api/project_badges/measure?project=eclipse-tractusx_traceability-foss-frontend&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=eclipse-tractusx_traceability-foss-frontend) [![Kics](https://github.com/eclipse-tractusx/traceability-foss/actions/workflows/kics.yml/badge.svg)](https://github.com/eclipse-tractusx/traceability-foss/actions/workflows/kics.yml) @@ -13,6 +14,9 @@ ## Table of Contents - [Trace-X](https://github.com/eclipse-tractusx/traceability-foss/#trace-x-is-a-system-for-tracking-parts-along-the-supply-chain) + - [Introduction](#introduction) + - [Vision and Mission Statement](#vision-and-mission-statement) + - [Trace-X Feature list](#trace-x-feature-list) - [How to contribute](https://github.com/eclipse-tractusx/traceability-foss/#how-to-contribute) - [Releasing](https://github.com/eclipse-tractusx/traceability-foss/#releasing) - [Environments](https://github.com/eclipse-tractusx/traceability-foss/#environments) @@ -31,7 +35,33 @@ - [Backend Testing strategy](#backend-testing-strategy) - [API documentation](https://github.com/eclipse-tractusx/traceability-foss/#api-documentation) - [Container Image](https://github.com/eclipse-tractusx/traceability-foss/#container-image) - - [License](https://github.com/eclipse-tractusx/traceability-foss/#license) + - [Licenses](https://github.com/eclipse-tractusx/traceability-foss/#licenses) + +## Introduction + +### Vision and Mission Statement +Trace-X empowers all companies from SMEs to large OEMs to participate in parts traceability with an Open-Source solution. + +The Open-Source Traceability application is developed within the Catena-X project and enables all companies to participate in Parts Traceability. +Trace-X offers capabilities to ingest data for serialized parts and batches as well as their child components.  Within CX, we strive to establish a standardized, data-sovereign and interoperable exchange of traceability data along the value creation chain. + +The application gives an overview of the supplier network and the supply chain. A high level of transparency across the supplier network enables faster intervention based on recorded events in the supply chain. Additionally, automated massages regarding Quality related incidents and a tool for inspecting the supply chain helps companies in these fast-moving times. + +All this saves' costs by seamlessly tracking parts as well as creates trust through clearly defined and secure data access by the companies and persons involved in the process. + +### Trace-X Feature list +Trace-X as the Open-Source solution for Parts Traceability offers the following functionalities: + +* List and view manufactured parts based on BoM AsBuild +* List and view planned parts based on BoM AsPlanned +* Filter and Search functionality on part views  +* Show detailed information on manufactured parts +** Asset Administration Shell description +** Submodel description such as SerialPartTypization, AssemblyPartRelationship, Batch +* List and view Supplier parts based on BoM As Build +** List of Supplier parts based on AssemblyPartRelationship Aspect +* View parts and parts relations in parts tree +* Send and receive quality investigations along the supply chain ## How to contribute @@ -123,9 +153,10 @@ See [TESTING](tx-backend/TESTING.md). The project follows [OpenAPI Specification](https://swagger.io/specification/) in order to document implemented REST Endpoints. The documentation can be found under [/openapi directory](https://github.com/eclipse-tractusx/traceability-foss/blob/main/tx-backend/openapi/traceability-foss-backend.json) or can be viewed in the Swagger UI accessing the url: `{projectBasePath}/api/swagger-ui/index.html` -## License +## Licenses -[Apache License 2.0](https://github.com/eclipse-tractusx/traceability-foss/blob/main/LICENSE) +* [Apache License 2.0](https://github.com/eclipse-tractusx/traceability-foss/blob/main/LICENSE) +* [CC BY 4.0 License](https://github.com/eclipse-tractusx/traceability-foss/blob/main/LICENSE_non-code) ## Notice for Docker image diff --git a/docs/RELEASE.md b/docs/RELEASE.md index a2d52d4eab..00dec9489d 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -3,6 +3,35 @@

Product Traceability FOSS Release guide

+# Release Numbering + +Trace-X using semantic versioning three-part version number. https://semver.org/ + +## Major Release +* If there are any incompatible API changes. +* Changes with high impact +* Contains new features and changes with critical business impact. +* Full regression tests are covered. + +## Minor Release +* Add functionality in a backwards compatible manner +* Features (backwards compatible has to be ensured) +* Minor release does not add features or changes with critical business impact. +* Regression impact should be low. +* No training effort necessary +* INT Test environment should be stable. No changes on depending on systems required. No changes on consumer side necessary. +* Operational risks should be low. + +## Bug fix / Patch Release +* Backwards compatible bug fixes +* Bug fixes and Hotfixes +* Covers Bug fixes and changes with no critical business impact. +* No changes on depending on or consuming systems required. +* INT Test environment should be stable. No changes on depending on systems required. No changes on consumer side necessary. + +## Tag +* Defined software state. + # Release an app ### Prerequisite: diff --git a/docs/api/traceability-foss-backend.json b/docs/api/traceability-foss-backend.json index bc56ac8afa..87277b3e7b 100644 --- a/docs/api/traceability-foss-backend.json +++ b/docs/api/traceability-foss-backend.json @@ -22,36 +22,142 @@ } ], "paths" : { - "/notifications/{notificationId}/edit" : { - "put" : { + "/policies" : { + "get" : { "tags" : [ - "Notifications" + "Policies" ], - "summary" : "Update notification by id", - "description" : "The endpoint updates notification by their id.", - "operationId" : "updateNotification", - "parameters" : [ - { - "name" : "notificationId", - "in" : "path", - "required" : true, - "schema" : { - "type" : "integer", - "format" : "int64" + "summary" : "Get all policies ", + "description" : "The endpoint returns all policies .", + "operationId" : "policy", + "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "200" : { + "description" : "Returns the policies", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/IrsPolicyResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security" : [ + { + "oAuth2" : [ + "profile email" + ] } + ] + }, + "put" : { + "tags" : [ + "Policies" ], + "summary" : "Updates policies ", + "description" : "The endpoint updates policies.", + "operationId" : "updatePolicy", "requestBody" : { "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/EditNotificationRequest" + "$ref" : "#/components/schemas/UpdatePolicyRequest" } } }, "required" : true }, "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "401" : { "description" : "Authorization failed.", "content" : { @@ -62,6 +168,26 @@ } } }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "415" : { "description" : "Unsupported media type", "content" : { @@ -72,11 +198,51 @@ } } }, - "204" : { - "description" : "No content." + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Update successful", + "content" : { + "application/json" : {} + } + } + }, + "security" : [ + { + "oAuth2" : [ + "profile email" + ] + } + ] + }, + "post" : { + "tags" : [ + "Policies" + ], + "summary" : "Create a policy ", + "description" : "The endpoint creates a policy.", + "operationId" : "createPolicy", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/RegisterPolicyRequest" + } + } + }, + "required" : true + }, + "responses" : { + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -95,6 +261,16 @@ } } }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "429" : { "description" : "Too many requests.", "content" : { @@ -105,8 +281,18 @@ } } }, - "500" : { - "description" : "Internal server error.", + "200" : { + "description" : "Returns the policies", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/CreatePolicyResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -115,8 +301,18 @@ } } }, - "404" : { - "description" : "Not found.", + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -135,15 +331,59 @@ ] } }, - "/bpn-config" : { - "get" : { + "/notifications/{notificationId}/edit" : { + "put" : { "tags" : [ - "BpnEdcMapping" + "Notifications" ], - "summary" : "Get BPN EDC URL mappings", - "description" : "The endpoint returns a result of BPN EDC URL mappings.", - "operationId" : "getBpnEdcs", + "summary" : "Update notification by id", + "description" : "The endpoint updates notification by their id.", + "operationId" : "updateNotification", + "parameters" : [ + { + "name" : "notificationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } + ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/EditNotificationRequest" + } + } + }, + "required" : true + }, "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "204" : { + "description" : "No content." + }, "401" : { "description" : "Authorization failed.", "content" : { @@ -154,8 +394,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -174,6 +414,55 @@ } } }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security" : [ + { + "oAuth2" : [ + "profile email" + ] + } + ] + } + }, + "/bpn-config" : { + "get" : { + "tags" : [ + "BpnEdcMapping" + ], + "summary" : "Get BPN EDC URL mappings", + "description" : "The endpoint returns a result of BPN EDC URL mappings.", + "operationId" : "getBpnEdcs", + "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "400" : { "description" : "Bad request.", "content" : { @@ -184,8 +473,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -194,8 +483,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -219,8 +508,28 @@ } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -261,8 +570,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -271,8 +580,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -281,8 +590,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -291,8 +600,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -301,8 +610,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -311,17 +620,12 @@ } } }, - "200" : { - "description" : "Returns the paged result found for BpnEdcMapping", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : 0, - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/BpnEdcMappingResponse" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } @@ -336,12 +640,17 @@ } } }, - "404" : { - "description" : "Not found.", + "200" : { + "description" : "Returns the paged result found for BpnEdcMapping", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "minItems" : 0, + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/BpnEdcMappingResponse" + } } } } @@ -378,8 +687,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -388,8 +697,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -398,8 +707,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -408,8 +717,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -418,8 +727,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -428,17 +737,12 @@ } } }, - "200" : { - "description" : "Returns the paged result found for BpnEdcMapping", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : 0, - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/BpnEdcMappingResponse" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } @@ -453,12 +757,17 @@ } } }, - "404" : { - "description" : "Not found.", + "200" : { + "description" : "Returns the paged result found for BpnEdcMapping", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "minItems" : 0, + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/BpnEdcMappingResponse" + } } } } @@ -492,18 +801,18 @@ } ], "responses" : { - "200" : { - "description" : "Returns submodel payload", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { - "type" : "string" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "401" : { - "description" : "Authorization failed.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -512,8 +821,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -522,8 +831,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -532,18 +841,18 @@ } } }, - "400" : { - "description" : "Bad request.", + "200" : { + "description" : "Returns submodel payload", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "type" : "string" } } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -552,8 +861,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -562,8 +871,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -606,14 +915,11 @@ } } }, - "required" : true - }, - "responses" : { - "200" : { - "description" : "Ok." - }, - "401" : { - "description" : "Authorization failed.", + "required" : true + }, + "responses" : { + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -622,8 +928,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -632,8 +938,11 @@ } } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -642,8 +951,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -652,8 +961,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -662,11 +971,8 @@ } } }, - "204" : { - "description" : "No Content." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -675,8 +981,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -684,6 +990,9 @@ } } } + }, + "204" : { + "description" : "No Content." } }, "security" : [ @@ -714,8 +1023,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -724,8 +1033,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -734,8 +1043,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -744,8 +1053,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -754,8 +1063,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -764,12 +1073,12 @@ } } }, - "201" : { - "description" : "Created.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/NotificationIdResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } @@ -784,12 +1093,12 @@ } } }, - "404" : { - "description" : "Not found.", + "201" : { + "description" : "Created.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/NotificationIdResponse" } } } @@ -834,8 +1143,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -844,8 +1153,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -857,8 +1166,8 @@ "204" : { "description" : "No content." }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -867,8 +1176,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -877,8 +1186,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -887,8 +1196,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -897,8 +1206,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -947,11 +1256,8 @@ "required" : true }, "responses" : { - "200" : { - "description" : "Ok." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -960,8 +1266,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -970,11 +1276,14 @@ } } }, + "200" : { + "description" : "Ok." + }, "204" : { "description" : "No content." }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -983,8 +1292,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -993,8 +1302,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1003,8 +1312,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1013,8 +1322,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1053,11 +1362,8 @@ } ], "responses" : { - "200" : { - "description" : "Ok." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1066,8 +1372,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1076,8 +1382,11 @@ } } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1086,8 +1395,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1096,8 +1405,11 @@ } } }, - "429" : { - "description" : "Too many requests.", + "204" : { + "description" : "No content." + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1106,11 +1418,8 @@ } } }, - "204" : { - "description" : "No content." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1119,8 +1428,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1159,11 +1468,8 @@ } ], "responses" : { - "200" : { - "description" : "Ok." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1172,8 +1478,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1182,8 +1488,11 @@ } } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1192,8 +1501,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1202,8 +1511,11 @@ } } }, - "429" : { - "description" : "Too many requests.", + "204" : { + "description" : "No content." + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1212,11 +1524,8 @@ } } }, - "204" : { - "description" : "No content." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1225,8 +1534,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1257,55 +1566,15 @@ "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/PageableFilterRequest" - } - } - }, - "required" : true - }, - "responses" : { - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } + "$ref" : "#/components/schemas/PageableFilterRequest" } } }, - "429" : { - "description" : "Too many requests.", + "required" : true + }, + "responses" : { + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1458,8 +1727,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1468,8 +1737,48 @@ } } }, - "404" : { - "description" : "Not found.", + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1507,8 +1816,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1517,8 +1826,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1527,8 +1836,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1537,8 +1846,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1547,8 +1856,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1557,12 +1866,12 @@ } } }, - "201" : { - "description" : "Created.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/CreateNotificationContractResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } @@ -1577,12 +1886,12 @@ } } }, - "404" : { - "description" : "Not found.", + "201" : { + "description" : "Created.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/CreateNotificationContractResponse" } } } @@ -1616,108 +1925,108 @@ "required" : true }, "responses" : { - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Bad request." + "message" : "Too many requests." } } } } }, - "401" : { - "description" : "Authorization failed.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Authorization failed." + "message" : "Bad request." } } } } }, - "200" : { - "description" : "Ok.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : 0, - "type" : "array", - "description" : "PageResults", - "items" : { - "$ref" : "#/components/schemas/PageResultContractResponse" + "type" : "string", + "example" : { + "message" : "Not found." } } } } }, - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Forbidden." + "message" : "Internal server error." } } } } }, - "429" : { - "description" : "Too many requests.", + "200" : { + "description" : "Ok.", "content" : { "application/json" : { "schema" : { - "type" : "string", - "example" : { - "message" : "Too many requests." + "maxItems" : 2147483647, + "minItems" : 0, + "type" : "array", + "description" : "PageResults", + "items" : { + "$ref" : "#/components/schemas/PageResultContractResponse" } } } } }, - "415" : { - "description" : "Unsupported media type.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Unsupported media type." + "message" : "Authorization failed." } } } } }, - "500" : { - "description" : "Internal server error.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Internal server error." + "message" : "Forbidden." } } } } }, - "404" : { - "description" : "Not found.", + "415" : { + "description" : "Unsupported media type.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Not found." + "message" : "Unsupported media type." } } } @@ -1764,11 +2073,8 @@ "required" : true }, "responses" : { - "204" : { - "description" : "No Content." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1777,8 +2083,11 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "204" : { + "description" : "No Content." + }, + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1787,8 +2096,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1797,8 +2106,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1807,8 +2116,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1817,8 +2126,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1827,8 +2136,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1880,11 +2189,8 @@ } }, "responses" : { - "204" : { - "description" : "No Content." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1893,8 +2199,11 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "204" : { + "description" : "No Content." + }, + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1903,18 +2212,18 @@ } } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "OK.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/ImportResponse" } } } }, - "400" : { - "description" : "Bad request.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1933,18 +2242,18 @@ } } }, - "200" : { - "description" : "OK.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ImportResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1953,8 +2262,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1992,8 +2301,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -2002,8 +2311,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -2012,8 +2321,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -2022,8 +2331,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -2032,8 +2341,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -2042,11 +2351,8 @@ } } }, - "201" : { - "description" : "Created." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -2055,8 +2361,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -2064,6 +2370,9 @@ } } } + }, + "201" : { + "description" : "Created." } }, "security" : [ @@ -2094,6 +2403,26 @@ "required" : true }, "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "401" : { "description" : "Authorization failed.", "content" : { @@ -2104,8 +2433,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -2124,8 +2453,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -2134,8 +2463,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -2335,26 +2664,6 @@ } } } - }, - "500" : { - "description" : "Internal server error.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } } }, "security" : [ @@ -2385,8 +2694,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -2395,8 +2704,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -2405,8 +2714,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -2415,8 +2724,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -2425,8 +2734,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -2435,11 +2744,8 @@ } } }, - "201" : { - "description" : "Created." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -2448,8 +2754,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -2457,6 +2763,9 @@ } } } + }, + "201" : { + "description" : "Created." } }, "security" : [ @@ -2679,8 +2988,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -2689,8 +2998,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -2699,8 +3008,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -2709,8 +3018,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -2719,8 +3028,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -2729,8 +3038,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -2739,8 +3048,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -2778,6 +3087,76 @@ } ], "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns the assets found", "content" : { @@ -2959,78 +3338,8 @@ "type" : "string", "example" : "TODO" } - } - } - } - } - } - }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "500" : { - "description" : "Internal server error.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + } + } } } } @@ -3072,6 +3381,26 @@ "required" : true }, "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "401" : { "description" : "Authorization failed.", "content" : { @@ -3082,8 +3411,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -3102,6 +3431,26 @@ } } }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns the updated asset", "content" : { @@ -3288,46 +3637,6 @@ } } } - }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "500" : { - "description" : "Internal server error.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } } }, "security" : [ @@ -3358,28 +3667,8 @@ } ], "responses" : { - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "403" : { - "description" : "Forbidden.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -3398,16 +3687,6 @@ } } }, - "429" : { - "description" : "Too many requests.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, "200" : { "description" : "Returns the assets found", "content" : { @@ -3595,6 +3874,46 @@ } } }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "500" : { "description" : "Internal server error.", "content" : { @@ -3604,7 +3923,44 @@ } } } + } + }, + "security" : [ + { + "oAuth2" : [ + "profile email" + ] + } + ] + }, + "patch" : { + "tags" : [ + "AssetsAsBuilt" + ], + "summary" : "Updates asset", + "description" : "The endpoint updates asset by provided quality type.", + "operationId" : "updateAsset_1", + "parameters" : [ + { + "name" : "assetId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } + ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UpdateAssetRequest" + } + } }, + "required" : true + }, + "responses" : { "404" : { "description" : "Not found.", "content" : { @@ -3614,44 +3970,67 @@ } } } - } - }, - "security" : [ - { - "oAuth2" : [ - "profile email" - ] - } - ] - }, - "patch" : { - "tags" : [ - "AssetsAsBuilt" - ], - "summary" : "Updates asset", - "description" : "The endpoint updates asset by provided quality type.", - "operationId" : "updateAsset_1", - "parameters" : [ - { - "name" : "assetId", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - } - ], - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/UpdateAssetRequest" - } - } }, - "required" : true - }, - "responses" : { + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns the updated asset", "content" : { @@ -3838,9 +4217,28 @@ } } } - }, - "401" : { - "description" : "Authorization failed.", + } + }, + "security" : [ + { + "oAuth2" : [ + "profile email" + ] + } + ] + } + }, + "/registry/reload" : { + "get" : { + "tags" : [ + "Registry" + ], + "summary" : "Triggers reload of shell descriptors", + "description" : "The endpoint Triggers reload of shell descriptors.", + "operationId" : "reload", + "responses" : { + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -3849,8 +4247,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -3859,8 +4257,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -3869,8 +4267,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -3879,8 +4277,11 @@ } } }, - "429" : { - "description" : "Too many requests.", + "202" : { + "description" : "Created registry reload job." + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -3889,8 +4290,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -3899,8 +4300,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -3919,17 +4320,27 @@ ] } }, - "/registry/reload" : { + "/policies/{policyId}" : { "get" : { "tags" : [ - "Registry" + "Policies" + ], + "summary" : "Gets policy by id", + "description" : "The endpoint returns policy by id.", + "operationId" : "getPolicyById", + "parameters" : [ + { + "name" : "policyId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], - "summary" : "Triggers reload of shell descriptors", - "description" : "The endpoint Triggers reload of shell descriptors.", - "operationId" : "reload", "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -3938,8 +4349,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -3948,21 +4359,18 @@ } } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Returns the policies", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/PolicyResponse" } } } }, - "202" : { - "description" : "Created registry reload job." - }, - "400" : { - "description" : "Bad request.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -3981,8 +4389,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -3991,8 +4399,18 @@ } } }, - "404" : { - "description" : "Not found.", + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -4009,19 +4427,27 @@ ] } ] - } - }, - "/policies" : { - "get" : { + }, + "delete" : { "tags" : [ "Policies" ], - "summary" : "Get all policies ", - "description" : "The endpoint returns all policies .", - "operationId" : "policy", + "summary" : "Deletes a policy ", + "description" : "The endpoint deletes policies.", + "operationId" : "deletePolicy", + "parameters" : [ + { + "name" : "policyId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } + ], "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -4030,18 +4456,14 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "200" : { + "description" : "Deletion successful", "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } + "application/json" : {} } }, - "403" : { - "description" : "Forbidden.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -4050,8 +4472,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -4070,18 +4492,18 @@ } } }, - "200" : { - "description" : "Returns the policies", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/PolicyResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4090,8 +4512,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -4130,23 +4552,18 @@ } ], "responses" : { - "200" : { - "description" : "OK.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "type" : "array", - "description" : "Notifications", - "items" : { - "$ref" : "#/components/schemas/NotificationResponse" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "401" : { - "description" : "Authorization failed.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -4155,18 +4572,23 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "200" : { + "description" : "OK.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "type" : "array", + "description" : "Notifications", + "items" : { + "$ref" : "#/components/schemas/NotificationResponse" + } } } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -4175,8 +4597,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -4185,8 +4607,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -4195,8 +4617,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4205,8 +4627,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -4270,9 +4692,29 @@ "RECEIVER" ] } - } - ], - "responses" : { + } + ], + "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns a distinct filter values for given fieldName.", "content" : { @@ -4298,8 +4740,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -4318,18 +4760,8 @@ } } }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4347,16 +4779,6 @@ } } } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } } }, "security" : [ @@ -4377,8 +4799,8 @@ "description" : "The endpoint can return limited data based on the user role", "operationId" : "dashboard", "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -4387,18 +4809,18 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "200" : { + "description" : "Returns dashboard data", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/DashboardResponse" } } } }, - "403" : { - "description" : "Forbidden.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -4407,8 +4829,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -4427,18 +4849,18 @@ } } }, - "200" : { - "description" : "Returns dashboard data", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/DashboardResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4447,8 +4869,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -4487,11 +4909,8 @@ } ], "responses" : { - "204" : { - "description" : "No Content." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -4500,8 +4919,11 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "204" : { + "description" : "No Content." + }, + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -4510,18 +4932,18 @@ } } }, - "200" : { - "description" : "OK.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ImportReportResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "403" : { - "description" : "Forbidden.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -4530,8 +4952,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -4540,8 +4962,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4560,12 +4982,12 @@ } } }, - "404" : { - "description" : "Not found.", + "200" : { + "description" : "OK.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/ImportReportResponse" } } } @@ -4607,6 +5029,46 @@ } ], "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns the paged result found for Asset", "content" : { @@ -4799,26 +5261,6 @@ } } }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, "403" : { "description" : "Forbidden.", "content" : { @@ -4829,18 +5271,8 @@ } } }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4858,16 +5290,6 @@ } } } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } } }, "security" : [ @@ -4941,23 +5363,18 @@ } ], "responses" : { - "200" : { - "description" : "Returns a distinct filter values for given fieldName.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : 0, - "type" : "array", - "items" : { - "type" : "string" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "401" : { - "description" : "Authorization failed.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -4966,18 +5383,23 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "200" : { + "description" : "Returns a distinct filter values for given fieldName.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "minItems" : 0, + "type" : "array", + "items" : { + "type" : "string" + } } } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -4986,8 +5408,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -4996,8 +5418,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -5006,8 +5428,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -5016,8 +5438,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -5052,9 +5474,29 @@ "schema" : { "type" : "string" } - } - ], - "responses" : { + } + ], + "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns the asset by childId", "content" : { @@ -5252,8 +5694,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -5272,18 +5714,8 @@ } } }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -5301,16 +5733,6 @@ } } } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } } }, "security" : [ @@ -5349,6 +5771,76 @@ } ], "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns the paged result found for Asset", "content" : { @@ -5540,76 +6032,6 @@ } } } - }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "500" : { - "description" : "Internal server error.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } } }, "security" : [ @@ -5683,23 +6105,18 @@ } ], "responses" : { - "200" : { - "description" : "Returns a distinct filter values for given fieldName.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : 0, - "type" : "array", - "items" : { - "type" : "string" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "401" : { - "description" : "Authorization failed.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -5708,18 +6125,23 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "200" : { + "description" : "Returns a distinct filter values for given fieldName.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "minItems" : 0, + "type" : "array", + "items" : { + "type" : "string" + } } } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -5728,8 +6150,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -5738,8 +6160,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -5748,8 +6170,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -5758,8 +6180,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -5802,8 +6224,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -5812,8 +6234,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -5822,8 +6244,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -5832,8 +6254,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -5842,8 +6264,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -5852,8 +6274,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -5862,8 +6284,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -6088,8 +6510,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -6098,8 +6520,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -6108,8 +6530,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -6118,8 +6540,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -6128,8 +6550,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -6138,8 +6560,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -6148,8 +6570,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -6177,11 +6599,8 @@ "description" : "Deletes all submodels from the system.", "operationId" : "deleteSubmodels", "responses" : { - "200" : { - "description" : "Ok." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -6190,8 +6609,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -6200,8 +6619,11 @@ } } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -6210,8 +6632,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -6220,8 +6642,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -6230,11 +6652,8 @@ } } }, - "204" : { - "description" : "No Content." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -6243,8 +6662,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -6252,6 +6671,9 @@ } } } + }, + "204" : { + "description" : "No Content." } }, "security" : [ @@ -6282,8 +6704,8 @@ } ], "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -6292,8 +6714,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -6302,8 +6724,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -6312,8 +6734,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -6322,8 +6744,11 @@ } } }, - "429" : { - "description" : "Too many requests.", + "204" : { + "description" : "Deleted." + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -6332,14 +6757,8 @@ } } }, - "200" : { - "description" : "Okay" - }, - "204" : { - "description" : "Deleted." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -6348,8 +6767,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -6357,6 +6776,9 @@ } } } + }, + "200" : { + "description" : "Okay" } }, "security" : [ @@ -6371,6 +6793,39 @@ }, "components" : { "schemas" : { + "UpdatePolicyRequest" : { + "type" : "object", + "properties" : { + "businessPartnerNumbers" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "policyIds" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "validUntil" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "ErrorResponse" : { + "type" : "object", + "properties" : { + "message" : { + "maxLength" : 1000, + "minLength" : 0, + "pattern" : "^.*$", + "type" : "string", + "example" : "Access Denied" + } + } + }, "EditNotificationRequest" : { "required" : [ "affectedPartIds", @@ -6428,18 +6883,6 @@ } } }, - "ErrorResponse" : { - "type" : "object", - "properties" : { - "message" : { - "maxLength" : 1000, - "minLength" : 0, - "pattern" : "^.*$", - "type" : "string", - "example" : "Access Denied" - } - } - }, "BpnMappingRequest" : { "required" : [ "bpn", @@ -6473,6 +6916,142 @@ } } }, + "Constraint" : { + "type" : "object", + "properties" : { + "leftOperand" : { + "type" : "string", + "example" : "string" + }, + "operator" : { + "$ref" : "#/components/schemas/Operator" + }, + "odrl:rightOperand" : { + "type" : "string", + "example" : "string" + } + } + }, + "Constraints" : { + "type" : "object", + "properties" : { + "and" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Constraint" + } + }, + "or" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Constraint" + } + } + } + }, + "Context" : { + "type" : "object", + "properties" : { + "odrl" : { + "type" : "string" + } + } + }, + "Operator" : { + "type" : "object", + "properties" : { + "@id" : { + "type" : "string", + "example" : "odrl:eq", + "enum" : [ + "eq", + "neq", + "lt", + "gt", + "in", + "lteq", + "gteq", + "isA", + "hasPart", + "isPartOf", + "isOneOf", + "isAllOf", + "isNoneOf" + ] + } + } + }, + "Payload" : { + "type" : "object", + "properties" : { + "@context" : { + "$ref" : "#/components/schemas/Context" + }, + "@id" : { + "type" : "string" + }, + "policy" : { + "$ref" : "#/components/schemas/Policy" + } + } + }, + "Permission" : { + "type" : "object", + "properties" : { + "action" : { + "type" : "string", + "example" : "use", + "enum" : [ + "access", + "use" + ] + }, + "constraint" : { + "$ref" : "#/components/schemas/Constraints" + } + } + }, + "Policy" : { + "type" : "object", + "properties" : { + "policyId" : { + "type" : "string", + "example" : "f253718e-a270-4367-901b-9d50d9bd8462" + }, + "createdOn" : { + "type" : "string", + "format" : "date-time" + }, + "validUntil" : { + "type" : "string", + "format" : "date-time" + }, + "permissions" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Permission" + } + } + } + }, + "RegisterPolicyRequest" : { + "type" : "object", + "properties" : { + "validUntil" : { + "type" : "string", + "format" : "date-time" + }, + "businessPartnerNumber" : { + "type" : "string" + }, + "payload" : { + "$ref" : "#/components/schemas/Payload" + } + } + }, + "CreatePolicyResponse" : { + "type" : "object" + }, "StartNotificationRequest" : { "required" : [ "receiverBpn", @@ -7248,6 +7827,57 @@ } } }, + "IrsPolicyResponse" : { + "type" : "object", + "properties" : { + "validUntil" : { + "type" : "string", + "format" : "date-time" + }, + "payload" : { + "$ref" : "#/components/schemas/Payload" + } + }, + "example" : [ + { + "validUntil" : "2025-12-12T23:59:59.999Z", + "payload" : { + "@context" : { + "@vocab" : "https://w3id.org/edc/v0.0.1/ns/", + "edc" : "https://w3id.org/edc/v0.0.1/ns/", + "cx-policy" : "https://w3id.org/catenax/policy/", + "odrl" : "http://www.w3.org/ns/odrl/2/" + }, + "@id" : "policy-id", + "policy" : { + "odrl:permission" : [ + { + "odrl:action" : "use", + "odrl:constraint" : { + "odrl:and" : [ + { + "odrl:leftOperand" : "Membership", + "odrl:operator" : { + "@id" : "odrl:eq" + }, + "odrl:rightOperand" : "active" + }, + { + "odrl:leftOperand" : "PURPOSE", + "odrl:operator" : { + "@id" : "odrl:eq" + }, + "odrl:rightOperand" : "ID 3.1 Trace" + } + ] + } + } + ] + } + } + } + ] + }, "ConstraintResponse" : { "type" : "object", "properties" : { @@ -7332,6 +7962,9 @@ "items" : { "$ref" : "#/components/schemas/PermissionResponse" } + }, + "businessPartnerNumber" : { + "type" : "string" } } }, diff --git a/docs/concept/#737-contract-parts-link/#737-contract-parts-link.md b/docs/concept/#737-contract-parts-link/#737-contract-parts-link.md new file mode 100644 index 0000000000..b47e320bde --- /dev/null +++ b/docs/concept/#737-contract-parts-link/#737-contract-parts-link.md @@ -0,0 +1,56 @@ +# \[Concept\] \[#737\] Contract parts link + +| Key | Value | +|---------------|--------------------------------------------------------------------------| +| Author | @ds-crehm | +| Creation date | 12.06.2024 | +| Ticket Id | [#737](https://github.com/eclipse-tractusx/traceability-foss/issues/737) | +| State | DRAFT | + +# Table of Contents +1. [Overview](#overview) +2. [Requirements](#requirements) +3. [Concept](#concept) +4. [Additional Details](#additional-details) + +# Overview +Administrators must be able to show parts that are related to a specific contract. + +# Requirements +- [ ] Frontend implementation done + - [ ] New action in contracts table + - [ ] New column "Contract ID" in AsBuilt and AsPlanned tables + - [ ] Action takes user to parts tables filtered by the specific contract +- [ ] Backend implementation done + - [ ] GET AssetsAsBuiltFilter can filter for contractAgreementId + - [ ] GET AssetsAsPlannedFilter can filter for contractAgreementId + +# Concept +## Backend +While the contractAgreementId is already saved for each part/asset, it's not possible to filter for it. +```json +{ + "message": "Provided field name: 'contractAgreementId' is not supported. Supported fields are following [businessPartner, nameAtManufacturer, receivedQualityAlertIdsInStatusActive, sentQualityInvestigationIdsInStatusActive, manufacturingDate, manufacturerPartId, importState, receivedQualityInvestigationIdsInStatusActive, manufacturingCountry, owner, semanticModelId, idShort, sentQualityAlertIdsInStatusActive, tractionBatteryCode, manufacturerId, semanticDataModel, productType, van, manufacturerName, customerPartId, nameAtCustomer, importNote, classification, id, qualityType, alerts]" +} +``` +So the API calls +GET AssetsAsBuiltFilter and GET AssetsAsPlannedFilter +must be changed to make it possible to filter for the contractAgreementId. + +## Frontend +See also: https://miro.com/app/board/uXjVO5JVoho= + +In the contracts table a new single-item action is added to the action menu. +The action is called "View parts" with the parts icon. +The tooltip when hovering over the action says "View parts using this contract". +![contracts-table-new-action.png](contracts-table-new-action.png) + +The column "Contract ID" must be added to part tables AsBuilt and AsPlanned. +![parts-tables-contract-column.png](parts-tables-contract-column.png) + +After clicking on the action, the user is taken to the parts table filtered for the selected contract. +Clicking back in the browser, brings the user back to the contracts table. +![parts-tables-contract-column-filtered.png](parts-tables-contract-column-filtered.png) + +# Additional Details +Given the dynamic nature of ongoing development, there might be variations between the conceptualization and the current implementation. For the latest status, refer to the documentation. diff --git a/docs/concept/#737-contract-parts-link/contracts-table-new-action.png b/docs/concept/#737-contract-parts-link/contracts-table-new-action.png new file mode 100644 index 0000000000..68e591b63c Binary files /dev/null and b/docs/concept/#737-contract-parts-link/contracts-table-new-action.png differ diff --git a/docs/concept/#737-contract-parts-link/parts-tables-contract-column-filtered.png b/docs/concept/#737-contract-parts-link/parts-tables-contract-column-filtered.png new file mode 100644 index 0000000000..cfcf904c47 Binary files /dev/null and b/docs/concept/#737-contract-parts-link/parts-tables-contract-column-filtered.png differ diff --git a/docs/concept/#737-contract-parts-link/parts-tables-contract-column.png b/docs/concept/#737-contract-parts-link/parts-tables-contract-column.png new file mode 100644 index 0000000000..bea1b8946a Binary files /dev/null and b/docs/concept/#737-contract-parts-link/parts-tables-contract-column.png differ diff --git a/docs/src/docs/user/user-manual.adoc b/docs/src/docs/user/user-manual.adoc index 19e2effc35..72c181fe04 100644 --- a/docs/src/docs/user/user-manual.adoc +++ b/docs/src/docs/user/user-manual.adoc @@ -59,7 +59,7 @@ To login use the credentials provided by the hosting company. Navigation is done based on the top menu. -image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/navigation/navigation-overview.png[] +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/navigation/navigation-overview.png[] === Dashboard @@ -92,17 +92,17 @@ Navigates to the Catena-X portal. Only applicable for the admin user role. Possibility to check the network status based on logfiles and will provide access to configuration possibilities for the application. -==== Contracts view and export +==== Contracts - view and export In the Contracts view an admin user can view contract agreements and sort them by the contract ID. Also, it's possible to select contracts and export/download them as a .csv file. -image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/navigation/admin_contract_view.png[] +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/navigation/admin_contract_view.png[] By clicking on the burger menu of a data row you can get to the detailed view of a contract. -==== Contract detailed view +===== Contract detailed view The contract detailed view is divided into two sections. @@ -114,9 +114,9 @@ displayed in JSON format. Use the view selector to switch between JSON view and JSON tree view. Expand the policy details card on the right upper side for full-width display. -image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/navigation/admin_contract_detailed_view.png[] +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/navigation/admin_contract_detailed_view.png[] -==== Data import functionality +==== Data provisioning With the admin user role, you have the ability to import data into the system. @@ -127,17 +127,79 @@ As you can see in the picture below, you can select a file to import and click o Find the example file at the following link: https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/tx-backend/testdata/import-test-data-CML1_v0.0.12.json -image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/navigation/admin_upload_file.png[] +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/navigation/admin_upload_file.png[] The system will validate the file content. Upon successful validation, assets will be saved as either "AssetAsPlanned" or "AssetAsBuilt", with the import state set to "transient." +=== Policy Management + +The policy management feature allows administrators to create, edit, view, and delete policies within the system. +This section provides an overview of how to use these features effectively. + +==== Policies List View + +The policies list view displays all the policies in a tabular format. +You can perform various actions such as view, edit, and delete policies from this view. + +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/policy-management/policies-list-view.png[] + +To access the policies list view, navigate to the "Policies" section from the top menu. + +By clicking on the settings symbol in the top right corner of the table, you are able to customize the visibility and order of the table columns. + +In the top left corner you can initiate the creation or deletion of policies. + +===== Deleting Policies + +To delete policies, follow these steps: + +Select the policies you wish to delete by checking the boxes next to them. +Click on the delete icon to open the deletion dialog. +Confirm the deletion in the dialog. +The system will then remove the selected policies and update the list view. +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/policy-management/delete-policies-dialog.png[] + +==== Policy Editor / Detailed View + +The policy editor allows you to create, edit, and view detailed information about a policy. + +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/policy-management/policy-editor.png[] + +Note: For existing policies, it is currently only possible to edit the valid until date and the BPN number. + +===== Creating a policy + +To create a policy: + +Navigate to the "Create Policy" section from the policies list view by clicking the plus icon in the top left corner of the table. +Fill in the policy details including policy name, validity date, BPN number(s), access type, and constraints. +Save the policy using the save button. +The system will validate the inputs and update the policy accordingly. +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/policy-management/policy-create.png[] + +==== Constraints + +Constraints define the conditions under which the policy is applicable. +You can add, edit, and remove constraints in the policy editor. + +To add a constraint: + +Click the add button in the constraints section. +Fill in the left operand, operator, and right operand. +Save the constraint. +To remove a constraint, click the delete button next to the constraint. + +To move constraints up or down in the list, use the up and down arrow buttons. + +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/policy-management/policy-constraints.png[] + === Sign out Sign out the current user and return to the Catena-X portal. === Language -image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/navigation/language-icon.png[] Change language. + +image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/navigation/language-icon.png[] Change language. + Supported languages: * English @@ -148,7 +210,7 @@ Supported languages: List view of the own manufactured (asBuilt) or planned (asPlanned) parts and batches as well as supplier/customer parts. You can adjust the view of tables by clicking on the fullscreen icon to maximize or minimize the view to the half of the full width. -image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/parts/parts-list-view.png[] +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/parts/parts-list-view.png[] === Parts table @@ -164,7 +226,7 @@ The global search bar at the top returns part results from both tables. Choosing the filter input field for any column and typing in any character will show filter suggestions. -image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/parts/parts-autosuggestion-filtering.png[] +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/parts/parts-autosuggestion-filtering.png[] === AsBuilt lifecycle parts @@ -195,12 +257,12 @@ Parts which exist in a quality notification will be highlighted as a yellow colo Select one or multiple parts that are in the AsBuilt lifecycle. A button will appear on the right of the lifecycle view selection: -image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/parts/publish_assets_button.png[] +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/parts/publish_assets_button.png[] Selection will enable you to publish assets with the goal to persist them (import state "persistent"). With a click on the button a window will be opened, where the selected assets are displayed and a required policy must be selected: -image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/parts/publish_assets_view.png[] +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/parts/publish_assets_view.png[] The following table explains the different import state an asset can have: @@ -230,7 +292,7 @@ The following table explains the different import state an asset can have: On the right upper site of a table there is a settings icon in which you can set the table columns to a desired view. With a click on it a dialog opens where you can change the settings of the corresponding table: -image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/parts/other-parts-table-settings-dialog.png[] +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/parts/other-parts-table-settings-dialog.png[] Hide/show table columns by clicking on the checkbox or the column name. It is possible to hide/show all columns by clicking on the "All" - checkbox. @@ -250,7 +312,7 @@ The settings will be stored in the local storage of the browser and will be pers To open the detail view, click on the three dots icon of the desired item and select "View details". More detailed information on the asset is listed as well as a part tree that visually shows the parts relations. -image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/parts/parts-list-detailed-view.png[] +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/parts/parts-list-detailed-view.png[] ==== Overview @@ -267,7 +329,7 @@ A yellow border indicates that the part is a piece of a batch. It is possible to adjust the view of the relationships by dragging the mouse to the desired view. Zooming in/out can be done with the corresponding control buttons. -image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/parts/open-new-tab.png[] Open part tree in new tab to zoom, scroll and focus in a larger view. +image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/parts/open-new-tab.png[] Open part tree in new tab to zoom, scroll and focus in a larger view. A minimap on the bottom right provides an overview of the current position on the part tree. ==== Asset state @@ -303,7 +365,7 @@ a tooltip will provide information explaining the reason. You can trigger the to To open the detail view, click on the three dots icon of the desired item from the parts table and select "View details". More detailed information on the asset is listed. -image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/parts/supplier-parts-list-detailed-view.png[] +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/parts/supplier-parts-list-detailed-view.png[] ==== Overview @@ -328,7 +390,7 @@ Customer Parts that are in a quality alert are highlighted yellow. To open the detail view, click on the three dots icon of the desired item and select "View details". More detailed information on the asset is listed. -image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/parts/customer-parts-list-detailed-view.png[] +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/parts/customer-parts-list-detailed-view.png[] ==== Overview @@ -347,20 +409,20 @@ Information about the identifiers at the customer for the respective part/batch. Inbox for received/sent quality notifications. -image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/quality-notifications/investigations-list-view.png[] +image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/quality-notifications/investigations-list-view.png[] The tables can be sorted, filtered and searched. Choosing the filter input field for any column and typing in any character will show filter suggestions. -image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/quality-notifications/investigations-autosuggestion-filtering.png[] +image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/quality-notifications/investigations-autosuggestion-filtering.png[] -image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/quality-notifications/notification-drafts.png[] Received quality notifications. +image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/quality-notifications/notification-drafts.png[] Received quality notifications. Quality notifications received by a customer. Those notifications specify a defect or request to investigate on a specific part / batch on your side and give feedback to the customer. -image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/quality-notifications/notification-send.png[] Sent quality notifications. +image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/quality-notifications/notification-send.png[] Sent quality notifications. Notifications in the context of quality investigations that are in queued/draft status or already requested/sent to the supplier. Those notifications specify a defect or request to investigate on a specific part / batch on your suppliers side and give feedback back to you. @@ -389,7 +451,7 @@ Notifications can be selected with the checkboxes on the left of the table. With the selection, there is a context menu for actions on mulitple (selected) notifications. The "more" menu is opened by clicking on the horizontally aligned three dots icon. -image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/quality-notifications/inbox-multiselect-actions.png[] +image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/quality-notifications/inbox-multiselect-actions.png[] === Quality notification create/edit view @@ -400,7 +462,7 @@ A quality notification can be started by the following options: A quality notification can be edited by clicking on the context menu on an item within the inbox. -image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/quality-notifications/investigation-create-view.png[] +image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/quality-notifications/investigation-create-view.png[] === Quality notifications context action @@ -409,11 +471,11 @@ Select the three dots icon on the right side of an quality notification entry to From there it is possible to open the quality notification detailed view or change the status of it. Only the possible status transition will show up. -image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/quality-notifications/notification-context-action.png[] +image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/quality-notifications/notification-context-action.png[] Changing the status of a quality notification will open a modal in which the details to the status change can be provided and completed. -image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/quality-notifications/investigation-context-action-modal.png[] +image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/quality-notifications/investigation-context-action-modal.png[] A pop-up will notify you if the status transition was successful. @@ -421,7 +483,7 @@ A pop-up will notify you if the status transition was successful. The quality notification detail view can be opened by selecting the corresponding option in the context menu. -image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/quality-notifications/investigation-detail-view.png[] +image:https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/quality-notifications/investigation-detail-view.png[] ==== Overview @@ -491,4 +553,4 @@ The receiver can never change the status to closed. The legend in the below state diagram describes who can set the status. One exception to this rule: the transition from status SENT to status RECEIVED is done automatically once the sender receives the Http status code 201. -image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/arc42/user-manual/quality-notifications/notificationstatemodel.png[Notification state model] +image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/quality-notifications/notificationstatemodel.png[Notification state model] diff --git a/docs/src/images/user-manual/policy-management/delete-policies-dialog.png b/docs/src/images/user-manual/policy-management/delete-policies-dialog.png new file mode 100644 index 0000000000..3f42946e0b Binary files /dev/null and b/docs/src/images/user-manual/policy-management/delete-policies-dialog.png differ diff --git a/docs/src/images/user-manual/policy-management/policies-list-view.png b/docs/src/images/user-manual/policy-management/policies-list-view.png new file mode 100644 index 0000000000..52058c5095 Binary files /dev/null and b/docs/src/images/user-manual/policy-management/policies-list-view.png differ diff --git a/docs/src/images/user-manual/policy-management/policy-constraints.png b/docs/src/images/user-manual/policy-management/policy-constraints.png new file mode 100644 index 0000000000..4dfbd00720 Binary files /dev/null and b/docs/src/images/user-manual/policy-management/policy-constraints.png differ diff --git a/docs/src/images/user-manual/policy-management/policy-create.png b/docs/src/images/user-manual/policy-management/policy-create.png new file mode 100644 index 0000000000..0aae8e66f4 Binary files /dev/null and b/docs/src/images/user-manual/policy-management/policy-create.png differ diff --git a/docs/src/images/user-manual/policy-management/policy-editor.png b/docs/src/images/user-manual/policy-management/policy-editor.png new file mode 100644 index 0000000000..23b9443770 Binary files /dev/null and b/docs/src/images/user-manual/policy-management/policy-editor.png differ diff --git a/frontend/src/app/mocks/services/policy-mock/policy.handler.ts b/frontend/src/app/mocks/services/policy-mock/policy.handler.ts index a4a019abe6..504045f0ea 100644 --- a/frontend/src/app/mocks/services/policy-mock/policy.handler.ts +++ b/frontend/src/app/mocks/services/policy-mock/policy.handler.ts @@ -18,12 +18,31 @@ ********************************************************************************/ import { environment } from '@env'; import { rest } from 'msw'; -import { getPolicies } from './policy.model'; +import { getPolicies, getPolicyById } from './policy.model'; export const policyHandler = (_ => { return [ rest.get(`*${ environment.apiUrl }/policies`, (req, res, ctx) => { return res(ctx.status(200), ctx.json(getPolicies())); - }) + }), + + rest.post(`*${ environment.apiUrl }/policies`, (req, res, ctx) => { + return res(ctx.status(201), ctx.json('success')); + }), + + rest.get(`*${ environment.apiUrl }/policies/:policyId`, (req, res, ctx) => { + const { policyId } = req.params; + const policy = getPolicyById(policyId); + return res(ctx.status(200), ctx.json(policy)); + }), + + rest.put(`*${ environment.apiUrl }/policies`, (req, res, ctx) => { + return res(ctx.status(200), ctx.json('success')); + }), + + rest.delete(`*${ environment.apiUrl }/policies/:policyId`, (req, res, ctx) => { + return res(ctx.status(200), ctx.json('success')); + }), + ] })(); diff --git a/frontend/src/app/mocks/services/policy-mock/policy.model.spec.ts b/frontend/src/app/mocks/services/policy-mock/policy.model.spec.ts new file mode 100644 index 0000000000..6a283ea82f --- /dev/null +++ b/frontend/src/app/mocks/services/policy-mock/policy.model.spec.ts @@ -0,0 +1,31 @@ +import { getOperatorTypeSign, OperatorType } from '@page/policies/model/policy.model'; + +describe('getOperatorTypeSign', () => { + it('should return "=" for OperatorType.EQ', () => { + expect(getOperatorTypeSign(OperatorType.EQ)).toBe('='); + }); + + it('should return "!=" for OperatorType.NEQ', () => { + expect(getOperatorTypeSign(OperatorType.NEQ)).toBe('!='); + }); + + it('should return "<" for OperatorType.LT', () => { + expect(getOperatorTypeSign(OperatorType.LT)).toBe('<'); + }); + + it('should return ">" for OperatorType.GT', () => { + expect(getOperatorTypeSign(OperatorType.GT)).toBe('>'); + }); + + it('should return "<=" for OperatorType.LTEQ', () => { + expect(getOperatorTypeSign(OperatorType.LTEQ)).toBe('<='); + }); + + it('should return ">=" for OperatorType.GTEQ', () => { + expect(getOperatorTypeSign(OperatorType.GTEQ)).toBe('>='); + }); + + it('should return the string representation of the type for unknown types', () => { + expect(getOperatorTypeSign('UNKNOWN' as OperatorType)).toBe('UNKNOWN'); + }); +}); diff --git a/frontend/src/app/mocks/services/policy-mock/policy.model.ts b/frontend/src/app/mocks/services/policy-mock/policy.model.ts index e80e7d4637..84e0a8f605 100644 --- a/frontend/src/app/mocks/services/policy-mock/policy.model.ts +++ b/frontend/src/app/mocks/services/policy-mock/policy.model.ts @@ -1,3 +1,5 @@ +import { OperatorType, Policy, PolicyAction, PolicyResponseMap } from '@page/policies/model/policy.model'; + /******************************************************************************** * Copyright (c) 2022, 2023, 2024 Contributors to the Eclipse Foundation * @@ -17,36 +19,183 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ // For now Mocks are built by current response (any). This is because Policy Model changes frequently -export const getPolicies = () => [ +export const getPolicies = (): Policy[] => { + return mockedPolicyList; +}; + +export const getPolicyById = (policyId: string | ReadonlyArray): Policy => { + return mockedPolicyList.filter(policy => policy.policyId === policyId)[0]; +}; + + +export const mockedPolicyList: Policy[] = [ { - policyId: "Mocked_Policy_Id", - createdOn: 1706690077.834424001, - validUntil: 1727740799.990000000, - permissions: [ + 'policyId': 'default-policy', + 'bpn': 'BPNL00000003CML1,BPNL00000003CNKC', + 'createdOn': '2024-05-29T06:18:40Z', + 'validUntil': '2029-05-29T06:18:40Z', + 'permissions': [ { - action: "USE", - constraints: [ - { + 'action': PolicyAction.USE, + 'constraints': { + 'and': null, + 'or': [ + { + 'leftOperand': 'cx-policy:FrameworkAgreement', + 'operatorTypeResponse': OperatorType.EQ, + 'rightOperand': 'traceability:1.0', + }, + { + 'leftOperand': 'cx-policy:UsagePurpose', + 'operatorTypeResponse': OperatorType.EQ, + 'rightOperand': 'cx.core.industrycore:1', + }, + ], + }, + }, + ], + }, + { + 'policyId': 'default-policy-2', + 'bpn': 'BPNL00000003CML1', + 'createdOn': '2024-05-29T06:18:40Z', + 'validUntil': '2029-05-29T06:18:40Z', + 'permissions': [ + { + 'action': PolicyAction.USE, + 'constraints': { + 'and': [ + { + 'leftOperand': 'cx-policy:FrameworkAgreement', + 'operatorTypeResponse': OperatorType.EQ, + 'rightOperand': 'traceability:1.0', + }, + { + 'leftOperand': 'cx-policy:UsagePurpose', + 'operatorTypeResponse': OperatorType.EQ, + 'rightOperand': 'cx.core.industrycore:1', + }, + ], + 'or': null, + }, + }, + ], + }, +]; + +const mockedPolicies = { + page: 0, + pageCount: 0, + pageSize: 10, + totalItems: 2, + + content: [ + { + bpnSelection: [ 'BPN10000000OEM0A', 'BPN10000000OEM0B' ], + policyName: 'Mocked_Policy_Name_1', + policyId: 'Mocked_Policy_1', + accessType: PolicyAction.ACCESS, + createdOn: '2024-05-29T06:18:40Z', + validUntil: '2024-05-29T06:18:40Z', + constraints: [ 'Membership = active', 'AND FrameworkAgreement.traceability in [active]', 'AND PURPOSE = ID 3.1 Trace' ], + permissions: [ + { + action: PolicyAction.USE, + constraint: { + and: [ + { + leftOperand: 'PURPOSE', + operator: { + id: OperatorType.EQ, + }, + rightOperand: 'ID 3.0 Trace', + }, + ], + or: [ + { + leftOperand: 'PURPOSE', + operator: { + id: OperatorType.EQ, + }, + rightOperand: 'ID 3.0 Trace', + }, + ], + }, + }, + ], + }, + { + bpnSelection: [ 'BPN10000000OEM0A', 'BPN10000000OEM0B' ], + policyName: 'Mocked_Policy_Name_2', + policyId: 'Mocked_Policy_2', + accessType: PolicyAction.USE, + createdOn: '2024-05-29T06:18:40Z', + validUntil: '2024-05-29T06:18:40Z', + constraints: [ 'PURPOSE = ID 3.1 Trace', 'OR PURPOSE = ID 3.0 Trace' ], + permissions: [ + { + action: PolicyAction.USE, + constraint: { and: [ { - leftOperand: "PURPOSE", - operatorTypeResponse: "EQ", - rightOperands: [ - "ID 3.0 Trace" - ] + leftOperand: 'PURPOSE', + operator: { + id: OperatorType.IN, + }, + rightOperand: 'BMW', }, + ], + or: [ { - leftOperand: "PURPOSE", - operatorTypeResponse: "EQ", - rightOperands: [ - "ID 3.0 Trace" - ] - } + leftOperand: 'PURPOSE', + operator: { + id: OperatorType.EQ, + }, + rightOperand: 'ID 3.0 Trace', + }, ], - or: [] - } - ] - } - ] - } -] + }, + }, + ], + }, + + ], + +}; + +export const MockPolicyResponseMap: PolicyResponseMap = { + 'default': [ + { + 'validUntil': '2024-06-30T11:07:00Z', + 'payload': { + '@context': { + 'odrl': 'http://www.w3.org/ns/odrl/2/', + }, + '@id': 'asdadasdas', + 'policy': { + 'policyId': 'asdadasdas', + 'createdOn': '2024-06-13T09:07:32.229901783Z', + 'validUntil': '2024-06-30T11:07:00Z', + 'permissions': [ + { + 'action': PolicyAction.USE, + 'constraint': { + 'and': null, + 'or': [ + { + 'leftOperand': 'asd', + 'operator': { + '@id': OperatorType.EQ, + }, + 'odrl:rightOperand': 'dsa', + }, + ], + }, + }, + ], + }, + }, + }, + ], +}; + diff --git a/frontend/src/app/modules/core/user/table-settings.service.ts b/frontend/src/app/modules/core/user/table-settings.service.ts index ae3d6b893e..5d4a15bbe4 100644 --- a/frontend/src/app/modules/core/user/table-settings.service.ts +++ b/frontend/src/app/modules/core/user/table-settings.service.ts @@ -23,6 +23,7 @@ import { NotificationsReceivedConfigurationModel } from '@shared/components/part import { NotificationsSentConfigurationModel } from '@shared/components/parts-table/notifications-sent-configuration.model'; import { PartsAsBuiltConfigurationModel } from '@shared/components/parts-table/parts-as-built-configuration.model'; import { PartsAsPlannedConfigurationModel } from '@shared/components/parts-table/parts-as-planned-configuration.model'; +import { PoliciesConfigurationModel } from '@shared/components/parts-table/policies-configuration.model'; import { TableViewConfig } from '@shared/components/parts-table/table-view-config.model'; import { ToastService } from '@shared/components/toasts/toast.service'; import { Subject } from 'rxjs'; @@ -112,6 +113,8 @@ export class TableSettingsService { return new NotificationsSentConfigurationModel().filterConfiguration(); case TableType.RECEIVED_NOTIFICATION: return new NotificationsReceivedConfigurationModel().filterConfiguration(); + case TableType.POLICIES: + return new PoliciesConfigurationModel().filterConfiguration(); } } diff --git a/frontend/src/app/modules/page/admin/admin.module.ts b/frontend/src/app/modules/page/admin/admin.module.ts index d0331225a8..6549348455 100644 --- a/frontend/src/app/modules/page/admin/admin.module.ts +++ b/frontend/src/app/modules/page/admin/admin.module.ts @@ -19,29 +19,36 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {getI18nPageProvider} from '@core/i18n'; -import {AdminFacade} from '@page/admin/core/admin.facade'; -import {AdminService} from '@page/admin/core/admin.service'; -import {ContractDetailComponent} from '@page/admin/presentation/contracts/contract-detail/contract-detail.component'; -import {ContractsComponent} from '@page/admin/presentation/contracts/contracts.component'; -import {ContractsFacade} from '@page/admin/presentation/contracts/contracts.facade'; -import {ContractsState} from '@page/admin/presentation/contracts/contracts.state'; -import {ModalModule} from '@shared/modules/modal/modal.module'; -import {SharedModule} from '@shared/shared.module'; -import {TemplateModule} from '@shared/template.module'; -import {AdminRoutingModule} from './admin.routing'; -import {AdminComponent} from './presentation/admin.component'; -import {BpnConfigurationComponent} from './presentation/bpn-configuration/bpn-configuration.component'; -import {SaveBpnConfigModal} from './presentation/bpn-configuration/save-modal/save-modal.component'; -import {ImportJsonComponent} from './presentation/import-json/import-json.component'; -import {NgxJsonViewerModule} from "ngx-json-viewer"; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { MatLineModule } from '@angular/material/core'; +import { getI18nPageProvider } from '@core/i18n'; +import { AdminFacade } from '@page/admin/core/admin.facade'; +import { AdminService } from '@page/admin/core/admin.service'; +import { ContractDetailComponent } from '@page/admin/presentation/contracts/contract-detail/contract-detail.component'; +import { ContractsComponent } from '@page/admin/presentation/contracts/contracts.component'; +import { ContractsFacade } from '@page/admin/presentation/contracts/contracts.facade'; +import { ContractsState } from '@page/admin/presentation/contracts/contracts.state'; +import { DeletionDialogComponent } from '@page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component'; +import { PoliciesComponent } from '@page/admin/presentation/policy-management/policies/policies.component'; +import { PoliciesFacade } from '@page/admin/presentation/policy-management/policies/policies.facade'; +import { PoliciesState } from '@page/admin/presentation/policy-management/policies/policies.state'; +import { PolicyEditorComponent } from '@page/admin/presentation/policy-management/policy-editor/policy-editor.component'; +import { ModalModule } from '@shared/modules/modal/modal.module'; +import { PolicyService } from '@shared/service/policy.service'; +import { SharedModule } from '@shared/shared.module'; +import { TemplateModule } from '@shared/template.module'; +import { NgxJsonViewerModule } from 'ngx-json-viewer'; +import { AdminRoutingModule } from './admin.routing'; +import { AdminComponent } from './presentation/admin.component'; +import { BpnConfigurationComponent } from './presentation/bpn-configuration/bpn-configuration.component'; +import { SaveBpnConfigModal } from './presentation/bpn-configuration/save-modal/save-modal.component'; +import { ImportJsonComponent } from './presentation/import-json/import-json.component'; @NgModule({ - declarations: [ AdminComponent, BpnConfigurationComponent, SaveBpnConfigModal, ImportJsonComponent, ContractsComponent, ContractDetailComponent ], - imports: [CommonModule, TemplateModule, SharedModule, AdminRoutingModule, ModalModule, NgxJsonViewerModule], - providers: [ ...getI18nPageProvider('page.admin'), AdminService, AdminFacade, ContractsFacade, ContractsState ], + declarations: [ AdminComponent, BpnConfigurationComponent, SaveBpnConfigModal, ImportJsonComponent, ContractsComponent, ContractDetailComponent, PoliciesComponent, DeletionDialogComponent, PolicyEditorComponent ], + imports: [ CommonModule, TemplateModule, SharedModule, AdminRoutingModule, ModalModule, NgxJsonViewerModule, MatLineModule ], + providers: [ ...getI18nPageProvider('page.admin'), AdminService, AdminFacade, ContractsFacade, ContractsState, PoliciesFacade, PoliciesState, PolicyService ], }) export class AdminModule { } diff --git a/frontend/src/app/modules/page/admin/admin.routing.ts b/frontend/src/app/modules/page/admin/admin.routing.ts index bfa17d45d8..298a468063 100644 --- a/frontend/src/app/modules/page/admin/admin.routing.ts +++ b/frontend/src/app/modules/page/admin/admin.routing.ts @@ -27,6 +27,8 @@ import { BpnConfigurationComponent } from '@page/admin/presentation/bpn-configur import { ContractDetailComponent } from '@page/admin/presentation/contracts/contract-detail/contract-detail.component'; import { ContractsComponent } from '@page/admin/presentation/contracts/contracts.component'; import { ImportJsonComponent } from '@page/admin/presentation/import-json/import-json.component'; +import { PoliciesComponent } from '@page/admin/presentation/policy-management/policies/policies.component'; +import { PolicyEditorComponent } from '@page/admin/presentation/policy-management/policy-editor/policy-editor.component'; import { I18NEXT_NAMESPACE_RESOLVER } from 'angular-i18next'; export /** @type {*} */ @@ -67,6 +69,38 @@ const ADMIN_ROUTING: Routes = [ resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, canActivate: [ RoleGuard ], }, + { + path: KnownAdminRoutes.POLICY_MANAGEMENT, + pathMatch: 'full', + component: PoliciesComponent, + data: { i18nextNamespaces: [ 'page.admin' ] }, + resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, + canActivate: [ RoleGuard ], + }, + { + path: KnownAdminRoutes.POLICY_MANAGEMENT_EDIT, + pathMatch: 'full', + component: PolicyEditorComponent, + data: { i18nextNamespaces: [ 'page.admin' ] }, + resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, + canActivate: [ RoleGuard ], + }, + { + path: KnownAdminRoutes.POLICY_MANAGEMENT_CREATE, + pathMatch: 'full', + component: PolicyEditorComponent, + data: { i18nextNamespaces: [ 'page.admin' ] }, + resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, + canActivate: [ RoleGuard ], + }, + { + path: KnownAdminRoutes.POLICY_MANAGEMENT_DETAIL_VIEW, + pathMatch: 'full', + component: PolicyEditorComponent, + data: { i18nextNamespaces: [ 'page.admin' ] }, + resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, + canActivate: [ RoleGuard ], + }, ]; @NgModule({ diff --git a/frontend/src/app/modules/page/admin/core/admin.model.ts b/frontend/src/app/modules/page/admin/core/admin.model.ts index 3f2e83e1f1..0205a23ba5 100644 --- a/frontend/src/app/modules/page/admin/core/admin.model.ts +++ b/frontend/src/app/modules/page/admin/core/admin.model.ts @@ -20,15 +20,19 @@ ********************************************************************************/ -import {FormArray, FormControl, FormGroup} from '@angular/forms'; -import {CalendarDateModel} from '@core/model/calendar-date.model'; -import {Pagination, PaginationResponse} from '@core/model/pagination.model'; +import { FormArray, FormControl, FormGroup } from '@angular/forms'; +import { CalendarDateModel } from '@core/model/calendar-date.model'; +import { Pagination, PaginationResponse } from '@core/model/pagination.model'; export enum KnownAdminRoutes { BPN = 'configure-bpn', IMPORT = 'configure-import', CONTRACT = 'contracts', - CONTRACT_DETAIL_VIEW = 'contracts/:contractId' + CONTRACT_DETAIL_VIEW = 'contracts/:contractId', + POLICY_MANAGEMENT = 'policies', + POLICY_MANAGEMENT_EDIT = 'policies/edit/:policyId', + POLICY_MANAGEMENT_CREATE = 'policies/create', + POLICY_MANAGEMENT_DETAIL_VIEW = 'policies/:policyId', } diff --git a/frontend/src/app/modules/page/admin/presentation/admin.component.scss b/frontend/src/app/modules/page/admin/presentation/admin.component.scss index ad692e9da4..03169a2691 100644 --- a/frontend/src/app/modules/page/admin/presentation/admin.component.scss +++ b/frontend/src/app/modules/page/admin/presentation/admin.component.scss @@ -38,6 +38,7 @@ border: none; overflow: visible; position: relative; + height: 80vh; } .sidenav--item { @@ -82,7 +83,7 @@ .sidenav--toggle-button { position: absolute; - bottom: 30px; + bottom: 40vh; right: -20px; z-index: 1; } diff --git a/frontend/src/app/modules/page/admin/presentation/admin.component.ts b/frontend/src/app/modules/page/admin/presentation/admin.component.ts index cb4a9390eb..d1b049c9f6 100644 --- a/frontend/src/app/modules/page/admin/presentation/admin.component.ts +++ b/frontend/src/app/modules/page/admin/presentation/admin.component.ts @@ -52,6 +52,11 @@ export class AdminComponent { icon: 'assignment_ind', link: '/admin/contracts', }, + { + name: 'routing.adminPolicies', + icon: 'description', + link: '/admin/policies', + }, ]; constructor(router: Router) { diff --git a/frontend/src/app/modules/page/admin/presentation/bpn-configuration/bpn-configuration.component.ts b/frontend/src/app/modules/page/admin/presentation/bpn-configuration/bpn-configuration.component.ts index 689c4959e7..4868e3de8c 100644 --- a/frontend/src/app/modules/page/admin/presentation/bpn-configuration/bpn-configuration.component.ts +++ b/frontend/src/app/modules/page/admin/presentation/bpn-configuration/bpn-configuration.component.ts @@ -21,15 +21,16 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; -import { BpnConfig, BpnConfigFormGroup } from '@page/admin/core/admin.model'; import { AdminFacade } from '@page/admin/core/admin.facade'; -import { BehaviorSubject, combineLatest, Observable, of, Subject, Subscription, tap } from 'rxjs'; -import { BaseInputHelper } from '@shared/abstraction/baseInput/baseInput.helper'; -import { BpnConfigEntry, ChangedInformation } from './bpn-configuration.model'; +import { BpnConfig, BpnConfigFormGroup } from '@page/admin/core/admin.model'; import { SaveBpnConfigModal } from '@page/admin/presentation/bpn-configuration/save-modal/save-modal.component'; +import { BaseInputHelper } from '@shared/abstraction/baseInput/baseInput.helper'; +import { BehaviorSubject, combineLatest, Observable, of, Subject, Subscription, tap } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { BpnConfigEntry, ChangedInformation } from './bpn-configuration.model'; export const bpnRegex = /^BPN[ALS][0-9A-Za-z]{10}[0-9A-Za-z]{2}$/; +export const bpnListRegex = /^(BPN[ALS][0-9A-Za-z]{10}[0-9A-Za-z]{2})(,\s*BPN[ALS][0-9A-Za-z]{10}[0-9A-Za-z]{2})*$/; @Component({ selector: 'app-bpn-configuration', diff --git a/frontend/src/app/modules/page/admin/presentation/contracts/contracts.component.spec.ts b/frontend/src/app/modules/page/admin/presentation/contracts/contracts.component.spec.ts index cf4d151bdd..141ea7e247 100644 --- a/frontend/src/app/modules/page/admin/presentation/contracts/contracts.component.spec.ts +++ b/frontend/src/app/modules/page/admin/presentation/contracts/contracts.component.spec.ts @@ -12,113 +12,117 @@ import { ContractsComponent } from './contracts.component'; describe('ContractTableComponent', () => { - const mockAdminFacade = { - getContracts: jasmine.createSpy().and.returnValue(of(getContracts)) - }; - - const renderContractTableComponent = () => renderComponent(ContractsComponent, {imports: [AdminModule], providers: [{provide: AdminFacade, useValue: mockAdminFacade}]}) - let createElementSpy: jasmine.Spy - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ContractsComponent], - providers: [AdminFacade, AdminService] - }); - createElementSpy = spyOn(document, 'createElement').and.callThrough(); - + const mockAdminFacade = { + getContracts: jasmine.createSpy().and.returnValue(of(getContracts)), + }; + + const renderContractTableComponent = () => renderComponent(ContractsComponent, { + imports: [ AdminModule ], + providers: [ { provide: AdminFacade, useValue: mockAdminFacade } ], + }); + + let createElementSpy: jasmine.Spy; + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ ContractsComponent ], + providers: [ AdminFacade, AdminService ], }); + createElementSpy = spyOn(document, 'createElement').and.callThrough(); + + }); + + it('should create', async () => { + const { fixture } = await renderContractTableComponent(); + const { componentInstance } = fixture; + expect(componentInstance).toBeTruthy(); + }); + + it('should filter and change table config', async () => { + const { fixture } = await renderContractTableComponent(); + const { componentInstance } = fixture; + + const mockFilter = { + contractId: [ 'hello' ], + counterpartyAddress: [], + creationDate: [], + endDate: [], + state: [], + }; + const myPagination = { page: 0, pageSize: 10, sorting: [ '', null ] as TableHeaderSort }; + componentInstance.onTableConfigChange(myPagination); + expect(componentInstance.pagination.pageSize).toEqual(10); - it('should create', async () => { - const {fixture} = await renderContractTableComponent(); - const {componentInstance} = fixture; - expect(componentInstance).toBeTruthy(); - }); - - it('should filter and change table config', async () => { - const {fixture} = await renderContractTableComponent(); - const {componentInstance} = fixture; - - const mockFilter = { - contractId: ["hello"], - counterpartyAddress: [], - creationDate: [], - endDate: [], - state: [] - } - const myPagination = {page: 0, pageSize: 10, sorting: ['', null] as TableHeaderSort} - componentInstance.onTableConfigChange(myPagination) - expect(componentInstance.pagination.pageSize).toEqual(10); - - componentInstance.filterActivated(mockFilter); + componentInstance.filterActivated(mockFilter); - expect(JSON.stringify(componentInstance.contractFilter)).toContain("hello"); + expect(JSON.stringify(componentInstance.contractFilter)).toContain('hello'); - }); + }); - it('select a contract', async () => { - const {fixture} = await renderContractTableComponent(); - const {componentInstance} = fixture; - let mockSelectedContract = assembleContract(getContracts().content[0]); - componentInstance.multiSelection([mockSelectedContract]); - expect(componentInstance.selectedContracts.length).toEqual(1); - expect(componentInstance.selectedContracts[0].contractId).toEqual(mockSelectedContract.contractId) - }); + it('select a contract', async () => { + const { fixture } = await renderContractTableComponent(); + const { componentInstance } = fixture; + let mockSelectedContract = assembleContract(getContracts().content[0]); + componentInstance.multiSelection([ mockSelectedContract ]); + expect(componentInstance.selectedContracts.length).toEqual(1); + expect(componentInstance.selectedContracts[0].contractId).toEqual(mockSelectedContract.contractId); + }); - it('should export contracts as csv', async () => { - const {fixture} = await renderContractTableComponent(); - const {componentInstance} = fixture; + it('should export contracts as csv', async () => { + const { fixture } = await renderContractTableComponent(); + const { componentInstance } = fixture; - let mockSelectedContract = assembleContract(getContracts().content[0]); - componentInstance.multiSelection([mockSelectedContract]); + let mockSelectedContract = assembleContract(getContracts().content[0]); + componentInstance.multiSelection([ mockSelectedContract ]); - let convertSpy = spyOn(componentInstance, 'convertArrayOfObjectsToCSV'); - let downloadSpy = spyOn(componentInstance, 'downloadCSV') - componentInstance.exportContractsAsCSV(); - expect(convertSpy).toHaveBeenCalledWith([assembleContract(getContracts().content[0])]); - expect(downloadSpy).toHaveBeenCalled(); + let convertSpy = spyOn(componentInstance, 'convertArrayOfObjectsToCSV'); + let downloadSpy = spyOn(componentInstance, 'downloadCSV'); + componentInstance.exportContractsAsCSV(); + expect(convertSpy).toHaveBeenCalledWith([ assembleContract(getContracts().content[0]) ]); + expect(downloadSpy).toHaveBeenCalled(); - }); + }); - it('should convert data to csv', async () => { - const {fixture} = await renderContractTableComponent(); - const {componentInstance} = fixture; + it('should convert data to csv', async () => { + const { fixture } = await renderContractTableComponent(); + const { componentInstance } = fixture; - let result = componentInstance.convertArrayOfObjectsToCSV([getContracts().content[0]]) + let result = componentInstance.convertArrayOfObjectsToCSV([ getContracts().content[0] ]); - expect(result).toEqual("contractId,counterpartyAddress,creationDate,endDate,state,policy\n" + - "abc1,https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp,2024-02-26T13:38:07+01:00,,Finalized,jsontextaspolicy"); + expect(result).toEqual('contractId,counterpartyAddress,creationDate,endDate,state,policy\n' + + 'abc1,https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp,2024-02-26T13:38:07+01:00,,Finalized,jsontextaspolicy'); - }); - it('should download CSV file', async () => { - const {fixture} = await renderContractTableComponent(); - const {componentInstance} = fixture; - const csvContent = 'header1,header2\nvalue1,value2\nvalue3,value4'; // Sample CSV content - const fileName = 'test.csv'; + }); + it('should download CSV file', async () => { + const { fixture } = await renderContractTableComponent(); + const { componentInstance } = fixture; + const csvContent = 'header1,header2\nvalue1,value2\nvalue3,value4'; // Sample CSV content + const fileName = 'test.csv'; - // Mock the required browser APIs - const link = document.createElement('a'); - spyOn(link, 'setAttribute'); - spyOn(link, 'click'); - spyOn(document.body, 'appendChild').and.callThrough(); - spyOn(document.body, 'removeChild').and.callThrough(); + // Mock the required browser APIs + const link = document.createElement('a'); + spyOn(link, 'setAttribute'); + spyOn(link, 'click'); + spyOn(document.body, 'appendChild').and.callThrough(); + spyOn(document.body, 'removeChild').and.callThrough(); - createElementSpy.and.returnValue(link); + createElementSpy.and.returnValue(link); - componentInstance.downloadCSV(csvContent, fileName); + componentInstance.downloadCSV(csvContent, fileName); - // Check if a link was created with correct attributes - expect(createElementSpy).toHaveBeenCalledWith('a'); - expect(link.setAttribute).toHaveBeenCalledWith('href', jasmine.any(String)); - expect(link.setAttribute).toHaveBeenCalledWith('download', fileName); - expect(link.style.visibility).toBe('hidden'); + // Check if a link was created with correct attributes + expect(createElementSpy).toHaveBeenCalledWith('a'); + expect(link.setAttribute).toHaveBeenCalledWith('href', jasmine.any(String)); + expect(link.setAttribute).toHaveBeenCalledWith('download', fileName); + expect(link.style.visibility).toBe('hidden'); - // Check if the link was appended to the document body - expect(document.body.appendChild).toHaveBeenCalledWith(link); + // Check if the link was appended to the document body + expect(document.body.appendChild).toHaveBeenCalledWith(link); - // Check if the link was clicked - expect(link.click).toHaveBeenCalled(); + // Check if the link was clicked + expect(link.click).toHaveBeenCalled(); - // Ensure that the link is removed from the document body after being clicked - expect(document.body.removeChild).toHaveBeenCalledWith(link); - }); + // Ensure that the link is removed from the document body after being clicked + expect(document.body.removeChild).toHaveBeenCalledWith(link); + }); }); diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component.html b/frontend/src/app/modules/page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component.html new file mode 100644 index 0000000000..bd7b6a8a77 --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component.html @@ -0,0 +1,28 @@ + +
+
+

{{ title | i18n }}

+ close + +
+
+

{{ 'pageAdmin.policyManagement.deletionText' | i18n }}

+ + +

{{ item }}

+
+
+
+
+
+ + +
+ +
+
diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component.scss b/frontend/src/app/modules/page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component.scss new file mode 100644 index 0000000000..bac5930034 --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component.scss @@ -0,0 +1,83 @@ +.dialog--content { + display: flex; + flex-direction: column; + width: 25vw; +} + +.dialog--header--container { + display: flex; + padding-bottom: 24px; +} + +.dialog--header--text { + font-weight: 500; + line-height: 1.6; + flex: 1; + font-family: LibreFranklin-SemiBold, -apple-system, + BlinkMacSystemFont, "Segoe UI", + Roboto, "Helvetica Neue", + Arial, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 24px; + text-align: center; + margin-left: 32px; +} + +.mat-icon { + width: 32px; + height: 32px; + font-size: 30px; + margin-top: 4px; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; +} + +.dialog--content--container { + display: flex; + font-size: 20px; + line-height: 1.6; +} + +.dialog--actions--container { + background-color: rgb(237, 240, 244); + width: 100%; + display: flex; + justify-content: center; + + .dialog--actions--save--button { + display: inline-flex; + -webkit-box-align: center; + align-items: center; + -webkit-box-pack: center; + justify-content: center; + position: relative; + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; + outline: 0px; + border: 0px; + margin: 0px; + cursor: pointer; + user-select: none; + vertical-align: middle; + appearance: none; + text-decoration: none; + font-family: LibreFranklin, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-weight: 500; + line-height: 1.5; + color: rgb(255, 255, 255); + min-width: 64px; + transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; + + background-color: rgb(15, 113, 203); + border-radius: 50px; + font-size: 18px; + padding: 14px 32px; + box-shadow: rgba(15, 113, 203, 0.4) 0px 0px 0px 3px; + } + + .dialog--actions--save--button:hover, .dialog--actions--save--button:active, .dialog--actions--save--button:focus { + background-color: rgb(13, 85, 175) + } +} diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component.spec.ts b/frontend/src/app/modules/page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component.spec.ts new file mode 100644 index 0000000000..baf3a1946a --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component.spec.ts @@ -0,0 +1,47 @@ +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { renderComponent } from '@tests/test-render.utils'; + +import { DeletionDialogComponent } from './deletion-dialog.component'; + +describe('DeletionDialogComponent', () => { + const dialogRefMock = { + close: jasmine.createSpy('close'), + }; + + const dataMock = { + policyIds: [ 'policy1', 'policy2' ], + title: 'Delete Policies', + }; + + const renderDeletionDialogComponent = () => renderComponent(DeletionDialogComponent, { + imports: [ MatDialogModule ], + providers: [ + { provide: MatDialogRef, useValue: dialogRefMock }, + { provide: MAT_DIALOG_DATA, useValue: dataMock }, + ], + }); + + it('should create', async () => { + const { fixture } = await renderDeletionDialogComponent(); + const { componentInstance } = fixture; + + expect(componentInstance).toBeTruthy(); + }); + + it('should initialize with provided data', async () => { + const { fixture } = await renderDeletionDialogComponent(); + const { componentInstance } = fixture; + + expect(componentInstance.policyIds).toEqual(dataMock.policyIds); + expect(componentInstance.title).toEqual(dataMock.title); + }); + + it('should call dialogRef.close(true) on save', async () => { + const { fixture } = await renderDeletionDialogComponent(); + const { componentInstance } = fixture; + + componentInstance.save(); + expect(dialogRefMock.close).toHaveBeenCalledWith(true); + }); +}); + diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component.ts b/frontend/src/app/modules/page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component.ts new file mode 100644 index 0000000000..8c1a2e7ea7 --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component.ts @@ -0,0 +1,22 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'app-deletion-dialog', + templateUrl: './deletion-dialog.component.html', + styleUrls: [ './deletion-dialog.component.scss' ], +}) +export class DeletionDialogComponent { + policyIds: string[]; + title: string; + + constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) { + this.policyIds = data?.policyIds; + this.title = data?.title; + } + + + save() { + this.dialogRef.close(true); + } +} diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.assembler.spec.ts b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.assembler.spec.ts new file mode 100644 index 0000000000..e4d5d0ece2 --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.assembler.spec.ts @@ -0,0 +1,121 @@ +import { PoliciesAssembler } from '@page/admin/presentation/policy-management/policies/policy.assembler'; +import { OperatorType, Policy, PolicyAction, PolicyEntry, PolicyResponseMap } from '@page/policies/model/policy.model'; + +// Mock data +const mockPolicy: Policy = { + policyId: 'policy123', + createdOn: '2024-01-01T00:00:00Z', + validUntil: '2024-12-31T23:59:59Z', + permissions: [ + { + action: 'use' as PolicyAction, + constraint: { + and: [ + { + leftOperand: 'left1', + operator: { '@id': OperatorType.EQ }, + operatorTypeResponse: OperatorType.EQ, + 'odrl:rightOperand': 'right1', + }, + ], + or: [ + { + leftOperand: 'left2', + operator: { '@id': OperatorType.NEQ }, + operatorTypeResponse: OperatorType.NEQ, + 'odrl:rightOperand': 'right2', + }, + ], + }, + }, + ], + +}; + +const mockPolicy2: Policy = { + policyId: 'policy123', + createdOn: '2024-01-01T00:00:00Z', + validUntil: '2024-12-31T23:59:59Z', + permissions: [ + { + action: 'use' as PolicyAction, + constraints: { + and: [ + { + leftOperand: 'left1', + operator: { '@id': OperatorType.EQ }, + operatorTypeResponse: OperatorType.EQ, + rightOperand: 'right1', + }, + ], + or: [ + { + leftOperand: 'left2', + operator: { '@id': OperatorType.NEQ }, + operatorTypeResponse: OperatorType.NEQ, + rightOperand: 'right2', + }, + ], + }, + }, + ], +}; + +const mockPolicyResponse: PolicyResponseMap = { + 'bpn123': [ + { + payload: { + '@context': { + odrl: 'test', + }, + '@id': 'entry123', + policy: mockPolicy, + }, + validUntil: '2024-01-01T00:00:00Z', + }, + ], +}; + +describe('PoliciesAssembler', () => { + it('should assemble policy', () => { + const assembledPolicy = PoliciesAssembler.assemblePolicy(mockPolicy2); + console.log(assembledPolicy.constraints); + expect(assembledPolicy.policyName).toBe(mockPolicy2.policyId); + expect(assembledPolicy.createdOn).toBe('2024-01-01T00:00'); + expect(assembledPolicy.validUntil).toBe('2024-12-31T23:59'); + expect(assembledPolicy.accessType).toBe('USE'); + expect(assembledPolicy.constraints).toEqual([ 'left1', '=', 'right1', 'left2', '!=', 'right2' ]); + + }); + + it('should map policy response to policy entry list', () => { + const policyEntryList = PoliciesAssembler.mapToPolicyEntryList(mockPolicyResponse); + expect(policyEntryList.length).toBe(1); + expect(policyEntryList[0].payload.policy.bpn).toBe('bpn123'); + expect(policyEntryList[0].payload.policy.policyName).toBe('entry123'); + }); + + it('should map display props to policy root level from policy entry', () => { + const policyEntry: PolicyEntry = { + validUntil: '2024-01-01T00:00:00Z', + payload: { + '@context': { + odrl: 'test', + }, + '@id': 'entry123', + policy: mockPolicy, + }, + }; + const constraints = PoliciesAssembler.mapDisplayPropsToPolicyRootLevelFromPolicyEntry(policyEntry); + expect(constraints).toEqual([ + 'left1', '=', 'right1', 'left2', '!=', 'right2', + ]); + }); + + it('should map display props to policy root level from policy', () => { + const constraints = PoliciesAssembler.mapDisplayPropsToPolicyRootLevelFromPolicy(mockPolicy2); + expect(constraints).toEqual([ + 'left1', '=', 'right1', 'left2', '!=', 'right2', + ]); + }); +}); diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.component.html b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.component.html new file mode 100644 index 0000000000..bbf81bbe5f --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.component.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.component.spec.ts b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.component.spec.ts new file mode 100644 index 0000000000..6d2a161208 --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.component.spec.ts @@ -0,0 +1,133 @@ +import { fakeAsync, tick } from '@angular/core/testing'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; +import { Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { RoleService } from '@core/user/role.service'; +import { AdminModule } from '@page/admin/admin.module'; +import { PoliciesFacade } from '@page/admin/presentation/policy-management/policies/policies.facade'; +import { Policy } from '@page/policies/model/policy.model'; +import { ToastService } from '@shared/components/toasts/toast.service'; +import { RenderResult } from '@testing-library/angular'; +import { renderComponent } from '@tests/test-render.utils'; +import { of, throwError } from 'rxjs'; +import { getPolicies } from '../../../../../../mocks/services/policy-mock/policy.model'; +import { PoliciesComponent } from './policies.component'; + +describe('PoliciesComponent', () => { + const policyFacadeMock = { + policies$: of(getPolicies()), + setPolicies: jasmine.createSpy('setPolicies'), + deletePolicies: jasmine.createSpy('deletePolicies').and.returnValue(of({})), + }; + + const toastServiceMock = { + success: jasmine.createSpy('success'), + error: jasmine.createSpy('error'), + }; + + const roleServiceMock = { + isAdmin: jasmine.createSpy('isAdmin').and.returnValue(true), + }; + + const routerMock = { + navigate: jasmine.createSpy('navigate'), + }; + + const matDialogMock = { + open: jasmine.createSpy('open').and.returnValue({ + afterClosed: () => of(true), + }), + }; + + const renderPoliciesComponent = async (): Promise> => await renderComponent(PoliciesComponent, { + imports: [ + AdminModule, + MatDialogModule, + RouterTestingModule, + ], + providers: [ + { provide: PoliciesFacade, useValue: policyFacadeMock }, + { provide: ToastService, useValue: toastServiceMock }, + { provide: RoleService, useValue: roleServiceMock }, + { provide: Router, useValue: routerMock }, + { provide: MatDialog, useValue: matDialogMock }, + ], + }); + + it('should create', async () => { + const { fixture } = await renderPoliciesComponent(); + const componentInstance = fixture.componentInstance; + + expect(componentInstance).toBeTruthy(); + }); + + it('should update selectedPolicies on multiSelection', async () => { + const { fixture } = await renderPoliciesComponent(); + const componentInstance = fixture.componentInstance; + const mockPolicies = [ { policyId: '1' }, { policyId: '2' } ] as Policy[]; + + componentInstance.multiSelection(mockPolicies); + expect(componentInstance.selectedPolicies).toEqual(mockPolicies); + }); + + it('should navigate to detailed view on openDetailedView', async () => { + const { fixture } = await renderPoliciesComponent(); + const componentInstance = fixture.componentInstance; + const mockPolicy = { policyId: '1' }; + + componentInstance.openDetailedView(mockPolicy); + expect(routerMock.navigate).toHaveBeenCalledWith([ 'admin/policies/1' ]); + }); + + it('should navigate to edit view on openEditView', async () => { + const { fixture } = await renderPoliciesComponent(); + const componentInstance = fixture.componentInstance; + const mockPolicy = { policyId: '1' }; + + componentInstance.openEditView(mockPolicy); + expect(routerMock.navigate).toHaveBeenCalledWith([ 'admin/policies/edit/1' ]); + }); + + it('should open deletion dialog and delete policies on confirmation', fakeAsync(async () => { + const { fixture } = await renderPoliciesComponent(); + const componentInstance = fixture.componentInstance; + componentInstance.selectedPolicies = [ { policyId: '1' }, { policyId: '2' } ] as Policy[]; + + componentInstance.openDeletionDialog(); + expect(matDialogMock.open).toHaveBeenCalled(); + + + tick(); // Simulate passage of time until afterClosed completes + + expect(policyFacadeMock.deletePolicies).toHaveBeenCalledWith(componentInstance.selectedPolicies); + expect(policyFacadeMock.setPolicies).toHaveBeenCalled(); + })); + + it('should call deletePolicies and handle success', fakeAsync(async () => { + const { fixture } = await renderPoliciesComponent(); + const componentInstance = fixture.componentInstance; + componentInstance.selectedPolicies = [ { policyId: '1' }, { policyId: '2' } ] as Policy[]; + + componentInstance.deletePolicies(); + + tick(); // Simulate passage of time until the observable completes + + expect(policyFacadeMock.deletePolicies).toHaveBeenCalledWith(componentInstance.selectedPolicies); + expect(policyFacadeMock.setPolicies).toHaveBeenCalled(); + })); + + it('should call deletePolicies and handle error', fakeAsync(async () => { + policyFacadeMock.deletePolicies.and.returnValue(throwError('error')); + + const { fixture } = await renderPoliciesComponent(); + const componentInstance = fixture.componentInstance; + componentInstance.selectedPolicies = [ { policyId: '1' }, { policyId: '2' } ] as Policy[]; + + componentInstance.deletePolicies(); + + tick(); // Simulate passage of time until the observable completes + + expect(policyFacadeMock.deletePolicies).toHaveBeenCalledWith(componentInstance.selectedPolicies); + expect(toastServiceMock.error).toHaveBeenCalledWith('pageAdmin.policyManagement.deleteError'); + })); +}); diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.component.ts b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.component.ts new file mode 100644 index 0000000000..2b1935e920 --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.component.ts @@ -0,0 +1,127 @@ +import { Component } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { Router } from '@angular/router'; +import { RoleService } from '@core/user/role.service'; +import { KnownAdminRoutes } from '@page/admin/core/admin.model'; +import { DeletionDialogComponent } from '@page/admin/presentation/policy-management/deletion-dialog/deletion-dialog.component'; +import { PoliciesFacade } from '@page/admin/presentation/policy-management/policies/policies.facade'; +import { Policy } from '@page/policies/model/policy.model'; +import { TableType } from '@shared/components/multi-select-autocomplete/table-type.model'; +import { + CreateHeaderFromColumns, + TableConfig, + TableEventConfig, + TableHeaderSort, +} from '@shared/components/table/table.model'; +import { ToastService } from '@shared/components/toasts/toast.service'; +import { setMultiSorting } from '@shared/helper/table-helper'; +import { View } from '@shared/model/view.model'; +import { Observable, Subject } from 'rxjs'; +import { take } from 'rxjs/operators'; + +@Component({ + selector: 'app-policies', + templateUrl: './policies.component.html', + styleUrls: [], +}) +export class PoliciesComponent { + policiesView$: Observable>; + tableConfig: TableConfig; + selectedPolicies: Policy[]; + policyFilter: any; + pagination: TableEventConfig; + multiSortList: TableHeaderSort[] = []; + ctrlKeyState: boolean = false; + deselectPartTrigger$ = new Subject(); + + constructor(public readonly policyFacade: PoliciesFacade, private readonly router: Router, private readonly toastService: ToastService, public dialog: MatDialog, private readonly roleService: RoleService) { + window.addEventListener('keydown', (event) => { + this.ctrlKeyState = setMultiSorting(event); + }); + window.addEventListener('keyup', (event) => { + this.ctrlKeyState = setMultiSorting(event); + }); + } + + ngOnInit() { + + this.pagination = { page: 0, pageSize: 10, sorting: [ '', null ] }; + this.tableConfig = { + displayedColumns: [ 'select', 'bpn', 'policyName', 'policyId', 'accessType', 'createdOn', 'validUntil', 'constraints', 'menu' ], + header: CreateHeaderFromColumns([ 'select', 'bpn', 'policyName', 'policyId', 'accessType', 'createdOn', 'validUntil', 'constraints', 'menu' ], 'pageAdmin.policies'), + menuActionsConfig: [ { + label: 'actions.edit', + icon: 'edit', + action: (selectedPolicy: Record) => this.openEditView(selectedPolicy), + isAuthorized: this.roleService.isAdmin(), + } ], + sortableColumns: { + select: false, + bpn: false, + policyName: false, + policyId: false, + accessType: false, + createdOn: false, + validUntil: false, + constraints: false, + menu: false, + }, + hasPagination: false, + }; + + this.policiesView$ = this.policyFacade.policies$; + this.policiesView$.pipe(take(2)).subscribe(data => { + if (data?.data?.length) { + return; + } else { + this.policyFacade.setPolicies(); + } + }); + + + } + + multiSelection(selectedPolicies: Policy[]) { + this.selectedPolicies = selectedPolicies; + } + + openDetailedView(selectedPolicy: Record) { + this.router.navigate([ 'admin/' + KnownAdminRoutes.POLICY_MANAGEMENT + '/' + selectedPolicy.policyId ]); + } + + openEditView(selectedPolicy: any) { + this.router.navigate([ 'admin/' + KnownAdminRoutes.POLICY_MANAGEMENT + '/edit/' + selectedPolicy.policyId ]); + } + + openDeletionDialog() { + const dialogRef = this.dialog.open(DeletionDialogComponent, { + data: { + policyIds: this.selectedPolicies.map(policy => policy.policyId), + title: 'pageAdmin.policyManagement.policyDeletion', + }, + }); + + dialogRef.afterClosed().subscribe(confirmation => { + if (confirmation) { + this.deletePolicies(); + this.deselectPartTrigger$.next(this.selectedPolicies); + } + }); + } + + + deletePolicies() { + this.policyFacade.deletePolicies(this.selectedPolicies).subscribe({ + next: value => { + this.toastService.success('pageAdmin.policyManagement.deleteSuccess'); + this.policyFacade.setPolicies(); + }, + error: err => { + this.toastService.error('pageAdmin.policyManagement.deleteError'); + }, + }); + } + + + protected readonly TableType = TableType; +} diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.facade.spec.ts b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.facade.spec.ts new file mode 100644 index 0000000000..405e02ee72 --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.facade.spec.ts @@ -0,0 +1,159 @@ +import { TestBed } from '@angular/core/testing'; +import { PoliciesState } from '@page/admin/presentation/policy-management/policies/policies.state'; // Adjust the path as necessary +import { PoliciesAssembler } from '@page/admin/presentation/policy-management/policies/policy.assembler'; // Adjust the path as necessary +import { Policy, PolicyEntry } from '@page/policies/model/policy.model'; // Adjust the path as necessary +import { PolicyService } from '@shared/service/policy.service'; // Adjust the path as necessary +import { of, throwError } from 'rxjs'; +import { PoliciesFacade } from './policies.facade'; // Adjust the path as necessary + +describe('PoliciesFacade', () => { + let facade: PoliciesFacade; + let policyServiceSpy: jasmine.SpyObj; + let policiesStateSpy: jasmine.SpyObj; + + beforeEach(() => { + const policyServiceMock = jasmine.createSpyObj('PolicyService', [ 'getPolicies', 'getPolicyById', 'deletePolicy', 'createPolicy', 'updatePolicy' ]); + const policiesStateMock = jasmine.createSpyObj('PoliciesState', [ 'policies$', 'selectedPolicy$', 'policies', 'selectedPolicy' ]); + + TestBed.configureTestingModule({ + providers: [ + PoliciesFacade, + { provide: PolicyService, useValue: policyServiceMock }, + { provide: PoliciesState, useValue: policiesStateMock }, + ], + }); + + facade = TestBed.inject(PoliciesFacade); + policyServiceSpy = TestBed.inject(PolicyService) as jasmine.SpyObj; + policiesStateSpy = TestBed.inject(PoliciesState) as jasmine.SpyObj; + }); + + it('should be created', () => { + expect(facade).toBeTruthy(); + }); + + describe('setPolicies', () => { + + it('should handle error when fetching policies', () => { + const mockError = new Error('Test error'); + policyServiceSpy.getPolicies.and.returnValue(throwError(mockError)); + + facade.setPolicies(); + + expect(policyServiceSpy.getPolicies).toHaveBeenCalled(); + expect(policiesStateSpy.policies).toEqual({ error: mockError }); + }); + }); + + describe('selectedPolicy', () => { + it('should get selected policy', () => { + const mockPolicy = { policyName: 'Test Policy' } as Policy; + policiesStateSpy.selectedPolicy = { data: mockPolicy }; + + expect(facade.selectedPolicy).toBe(mockPolicy); + }); + + it('should set selected policy', () => { + const mockPolicy = { policyName: 'Test Policy' } as Policy; + + facade.selectedPolicy = mockPolicy; + + expect(policiesStateSpy.selectedPolicy).toEqual({ data: mockPolicy }); + }); + }); + + describe('setSelectedPolicyById', () => { + it('should fetch and set selected policy by ID', () => { + const mockPolicy = { policyName: 'Test Policy' } as Policy; + policyServiceSpy.getPolicyById.and.returnValue(of(mockPolicy)); + spyOn(PoliciesAssembler, 'assemblePolicy').and.returnValue(mockPolicy); + + facade.setSelectedPolicyById('test-id'); + + expect(policyServiceSpy.getPolicyById).toHaveBeenCalledWith('test-id'); + expect(policiesStateSpy.selectedPolicy).toEqual({ data: mockPolicy }); + }); + + it('should handle error when fetching policy by ID', () => { + const mockError = new Error('Test error'); + policyServiceSpy.getPolicyById.and.returnValue(throwError(mockError)); + + facade.setSelectedPolicyById('test-id'); + + expect(policyServiceSpy.getPolicyById).toHaveBeenCalledWith('test-id'); + expect(policiesStateSpy.selectedPolicy).toEqual({ error: mockError }); + }); + }); + + describe('unsubscribePolicies', () => { + it('should unsubscribe from policies subscriptions', () => { + const mockPoliciesSubscription = jasmine.createSpyObj('Subscription', [ 'unsubscribe' ]); + const mockSelectedPoliciesSubscription = jasmine.createSpyObj('Subscription', [ 'unsubscribe' ]); + facade['policiesSubscription'] = mockPoliciesSubscription; + facade['selectedPoliciesSubscription'] = mockSelectedPoliciesSubscription; + + facade.unsubscribePolicies(); + + expect(mockPoliciesSubscription.unsubscribe).toHaveBeenCalled(); + expect(mockSelectedPoliciesSubscription.unsubscribe).toHaveBeenCalled(); + }); + }); + + describe('deletePolicies', () => { + it('should call policyService.deletePolicies with policy IDs', () => { + const mockPolicies = [ { policyId: '1' }, { policyId: '2' } ] as Policy[]; + const mockDeleteResponse1 = of(null); + const mockDeleteResponse2 = of(null); + + policyServiceSpy.deletePolicy.withArgs('1').and.returnValue(mockDeleteResponse1); + policyServiceSpy.deletePolicy.withArgs('2').and.returnValue(mockDeleteResponse2); + + const result = facade.deletePolicies(mockPolicies); + + // Expect the policyService.deletePolicy to be called with each policyId + expect(policyServiceSpy.deletePolicy).toHaveBeenCalledWith('1'); + expect(policyServiceSpy.deletePolicy).toHaveBeenCalledWith('2'); + + // Expect the result to be an observable that emits an array of results + result.subscribe(responses => { + expect(responses).toEqual([ null, null ]); + }); + }); + }); + + describe('createPolicy', () => { + it('should call policyService.createPolicy with policy entry', () => { + const mockPolicyEntry = { + policyName: 'New Policy', + validUntil: null, + payload: null, + businessPartnerNumber: '', + } as PolicyEntry; + const mockCreateResponse = of(null); + policyServiceSpy.createPolicy.and.returnValue(mockCreateResponse); + + const result = facade.createPolicy(mockPolicyEntry); + + expect(policyServiceSpy.createPolicy).toHaveBeenCalledWith(mockPolicyEntry); + expect(result).toBe(mockCreateResponse); + }); + }); + + describe('updatePolicy', () => { + it('should call policyService.updatePolicy with policy entry', () => { + const mockPolicyEntry = { + policyName: 'New Policy', + validUntil: null, + payload: null, + businessPartnerNumber: '', + } as PolicyEntry; + const mockUpdateResponse = of(null); + policyServiceSpy.updatePolicy.and.returnValue(mockUpdateResponse); + + const result = facade.updatePolicy(mockPolicyEntry); + + expect(policyServiceSpy.updatePolicy).toHaveBeenCalledWith(mockPolicyEntry); + expect(result).toBe(mockUpdateResponse); + }); + }); +}); diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.facade.ts b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.facade.ts new file mode 100644 index 0000000000..dbb29869cd --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.facade.ts @@ -0,0 +1,78 @@ +import { Injectable } from '@angular/core'; +import { PoliciesState } from '@page/admin/presentation/policy-management/policies/policies.state'; +import { PoliciesAssembler } from '@page/admin/presentation/policy-management/policies/policy.assembler'; +import { Policy, PolicyEntry } from '@page/policies/model/policy.model'; +import { View } from '@shared/model/view.model'; +import { PolicyService } from '@shared/service/policy.service'; +import { forkJoin, Observable, Subject, Subscription } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Injectable() +export class PoliciesFacade { + private policiesSubscription: Subscription; + private selectedPoliciesSubscription: Subscription; + private readonly unsubscribeTrigger = new Subject(); + + + constructor(private readonly policyService: PolicyService, + private readonly policiesState: PoliciesState, + ) { + } + + get policies$(): Observable> { + return this.policiesState.policies$; + } + + public setPolicies(): void { + this.policiesSubscription?.unsubscribe(); + this.policiesSubscription = this.policyService.getPolicies().pipe(map(response => { + return PoliciesAssembler.mapToPolicyEntryList(response).map(entry => entry.payload.policy).map(policy => PoliciesAssembler.assemblePolicy(policy)); + })).subscribe({ + next: data => (this.policiesState.policies = { data: data }), + error: error => (this.policiesState.policies = { error }), + }); + } + + get selectedPolicy$(): Observable> { + return this.policiesState.selectedPolicy$; + } + + get selectedPolicy(): Policy { + return this.policiesState.selectedPolicy?.data; + } + + + public set selectedPolicy(policy: Policy) { + this.policiesState.selectedPolicy = { data: policy }; + } + + public setSelectedPolicyById(policyId: string): void { + this.policyService.getPolicyById(policyId).subscribe({ + next: data => (this.policiesState.selectedPolicy = { data: PoliciesAssembler.assemblePolicy(data) }), + error: error => (this.policiesState.selectedPolicy = { error }), + }); + } + + + public unsubscribePolicies(): void { + this.policiesSubscription?.unsubscribe(); + this.selectedPoliciesSubscription?.unsubscribe(); + this.unsubscribeTrigger.next(); + } + + deletePolicies(selectedPolicies: Policy[]): Observable { + const deleteRequests = selectedPolicies.map(policy => + this.policyService.deletePolicy(policy.policyId), + ); + + return forkJoin(deleteRequests); + } + + createPolicy(policyEntry: PolicyEntry) { + return this.policyService.createPolicy(policyEntry); + } + + updatePolicy(policyEntry: PolicyEntry) { + return this.policyService.updatePolicy(policyEntry); + } +} diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.state.spec.ts b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.state.spec.ts new file mode 100644 index 0000000000..6e39b110ed --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.state.spec.ts @@ -0,0 +1,65 @@ +import { TestBed } from '@angular/core/testing'; +import { Policy } from '@page/policies/model/policy.model'; +import { View } from '@shared/model/view.model'; +import { PoliciesState } from './policies.state'; + +describe('PoliciesState', () => { + let policiesState: PoliciesState; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ PoliciesState ], + }); + policiesState = TestBed.inject(PoliciesState); + }); + + it('should be created', () => { + expect(policiesState).toBeTruthy(); + }); + + it('should set and get policies correctly', () => { + const mockPolicies: Policy[] = [ + { policyId: '1', policyName: 'Policy 1', validUntil: '123', permissions: [], createdOn: '' }, + { policyId: '2', policyName: 'Policy 2', validUntil: '123', permissions: [], createdOn: '' }, + ]; + const mockView: View = { + data: mockPolicies, + loader: false, + error: undefined, + }; + + // Set policies + policiesState.policies = mockView; + + // Verify get policies$ + policiesState.policies$.subscribe((policies) => { + expect(policies).toEqual(mockView); + }); + }); + + it('should set and get selected policy correctly', () => { + const mockSelectedPolicy: Policy = { + policyId: '1', + policyName: 'Selected Policy', + validUntil: '123', + permissions: [], + createdOn: '', + }; + const mockView: View = { + data: mockSelectedPolicy, + loader: false, + error: undefined, + }; + + // Set selected policy + policiesState.selectedPolicy = mockView; + + // Verify get selectedPolicy$ + policiesState.selectedPolicy$.subscribe((selectedPolicy) => { + expect(selectedPolicy).toEqual(mockView); + }); + + // Verify get selectedPolicy + expect(policiesState.selectedPolicy).toEqual(mockView); + }); +}); diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.state.ts b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.state.ts new file mode 100644 index 0000000000..a3068f0902 --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policies.state.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { Policy } from '@page/policies/model/policy.model'; +import { State } from '@shared/model/state'; +import { View } from '@shared/model/view.model'; +import { Observable } from 'rxjs'; + +@Injectable() +export class PoliciesState { + private readonly _policies$ = new State>({ loader: true }); + private readonly _selectedPolicy$: State> = new State>({ loader: true }); + + public get policies$(): Observable> { + return this._policies$.observable; + } + + public set policies({ data, loader, error }: View) { + const policiesView: View = { data, loader, error }; + this._policies$.update(policiesView); + } + + get selectedPolicy$(): Observable> { + return this._selectedPolicy$.observable; + } + + set selectedPolicy({ data, loader, error }: View) { + const selectedPolicyView: View = { data, loader, error }; + this._selectedPolicy$.update(selectedPolicyView); + } + + get selectedPolicy(): View { + return this._selectedPolicy$.snapshot; + } + +} diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policy.assembler.ts b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policy.assembler.ts new file mode 100644 index 0000000000..0a41d888ef --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policies/policy.assembler.ts @@ -0,0 +1,169 @@ +import { CalendarDateModel } from '@core/model/calendar-date.model'; +import { + getOperatorTypeSign, + OperatorType, + Policy, + PolicyAction, + PolicyEntry, + PolicyResponseMap, +} from '@page/policies/model/policy.model'; +import { isNumber } from 'lodash-es'; + +export class PoliciesAssembler { + public static assemblePolicy(policy: Policy): Policy { + const formattedCreatedOn = new CalendarDateModel(policy.createdOn as string); + const formattedValidUntil = new CalendarDateModel(policy.validUntil as string); + return { + ...policy, + policyName: policy.policyId, + createdOn: isNumber(policy.createdOn) ? new Date(policy.createdOn as number * 1000).toISOString().slice(0, 19) + 'Z' : (formattedCreatedOn.isInitial() ? null : formattedCreatedOn.valueOf().toISOString().slice(0, 16)), + validUntil: isNumber(policy.validUntil) ? new Date(policy.validUntil as number * 1000).toISOString().slice(0, 19) + 'Z' : (formattedValidUntil.isInitial() ? null : formattedValidUntil.valueOf().toISOString().slice(0, 16)), + accessType: policy.permissions[0].action.toUpperCase() as PolicyAction, + constraints: policy.constraints ?? this.mapDisplayPropsToPolicyRootLevelFromPolicy(policy), + }; + } + + public static mapToPolicyEntryList(policyResponse: PolicyResponseMap): PolicyEntry[] { + const list: PolicyEntry[] = []; + for (const [ key, value ] of Object.entries(policyResponse)) { + value.forEach((entry) => { + entry.payload.policy.bpn = key; + entry.payload.policy.constraints = this.mapDisplayPropsToPolicyRootLevelFromPolicyEntry(entry); + list.push(entry); + }); + } + return list; + } + + public static mapDisplayPropsToPolicyRootLevelFromPolicyEntry(entry: PolicyEntry): string[] { + entry.payload.policy.policyName = entry.payload['@id']; + entry.payload.policy.accessType = entry.payload.policy.permissions[0].action; + let constrainsList = []; + entry.payload.policy.permissions.forEach(permission => { + permission.constraint?.and?.forEach((andConstraint, index) => { + constrainsList.push(andConstraint.leftOperand); + constrainsList.push(getOperatorTypeSign(OperatorType[andConstraint.operator['@id'].toUpperCase()])); + constrainsList.push(andConstraint['odrl:rightOperand']); + if (index !== permission.constraint.and.length - 1) { + constrainsList.push(' AND '); + } + }); + permission.constraint?.or?.forEach((orConstraint, index) => { + constrainsList.push(orConstraint.leftOperand); + constrainsList.push(getOperatorTypeSign(OperatorType[orConstraint.operator['@id'].toUpperCase()])); + constrainsList.push(orConstraint['odrl:rightOperand']); + if (index !== permission.constraint.or.length - 1) { + constrainsList.push(' OR '); + } + }); + }); + return constrainsList; + } + + public static mapDisplayPropsToPolicyRootLevelFromPolicy(policy: Policy): string[] { + let constrainsList = []; + policy.permissions.forEach((permission) => { + permission.constraints?.and?.forEach((andConstraint, index) => { + constrainsList.push(andConstraint.leftOperand); + constrainsList.push(getOperatorTypeSign(andConstraint.operatorTypeResponse)); + constrainsList.push(andConstraint.rightOperand); + if (index !== permission.constraints.and.length - 1) { + constrainsList.push(' AND '); + } + }); + permission.constraints?.or?.forEach((orConstraint, index) => { + constrainsList.push(orConstraint.leftOperand); + constrainsList.push(getOperatorTypeSign(orConstraint.operatorTypeResponse)); + constrainsList.push(orConstraint.rightOperand); + if (index !== permission.constraints.or.length - 1) { + constrainsList.push(' OR '); + } + }); + }); + return constrainsList; + } + + /** + * This Feature is commented out for now, because uploading/downloading Templates/Policies is + * currently not a requirement but could be one in future. + */ + /* + public static validatePoliciesTemplate(data: any) { + + if (typeof data !== 'object' || data === null || !Array.isArray(data[Object.keys(data)[0]])) { + return false; + } + + for (const entry of data[Object.keys(data)[0]]) { + if (typeof entry.validUntil !== 'string') { + return false; + } + + const payload = entry.payload; + if (typeof payload !== 'object' || payload === null) { + return false; + } + + const context = payload['@context']; + if (typeof context !== 'object' || context === null) { + return false; + } + + if (typeof payload['@id'] !== 'string') { + return false; + } + + const policy = payload.policy; + if (typeof policy !== 'object' || policy === null) { + return false; + } + + if (typeof policy.policyId !== 'string' || + typeof policy.createdOn !== 'string' || + typeof policy.validUntil !== 'string' || + !Array.isArray(policy.permissions)) { + return false; + } + + for (const permission of policy.permissions) { + if (typeof permission.action !== 'string') { + return false; + } + + const constraint = permission.constraint; + if (typeof constraint !== 'object' || constraint === null || !Array.isArray(constraint.and) || (constraint.or !== null && !Array.isArray(constraint.or))) { + return false; + } + + if (constraint.and !== null) { + for (const andConstraint of constraint.and) { + if (typeof andConstraint.leftOperand !== 'string' || + typeof andConstraint.operator !== 'object' || + andConstraint.operator === null || + typeof andConstraint.operator['@id'] !== 'string' || + typeof andConstraint['odrl:rightOperand'] !== 'string') { + return false; + } + } + } + + if (constraint.or !== null) { + for (const orConstraint of constraint.or) { + if (typeof orConstraint.leftOperand !== 'string' || + typeof orConstraint.operator !== 'object' || + orConstraint.operator === null || + typeof orConstraint.operator['@id'] !== 'string' || + typeof orConstraint['odrl:rightOperand'] !== 'string') { + return false; + } + } + } + } + } + + return true; + } + + */ + +} diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-data.ts b/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-data.ts new file mode 100644 index 0000000000..f812fce838 --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-data.ts @@ -0,0 +1,36 @@ +import { ConstraintLogicType, OperatorType } from '@page/policies/model/policy.model'; + +export const OperatorTypesAsSelectOptionsList: any[] = Object.keys(OperatorType).map(key => { + let convertedOperatorType = ''; + switch (key) { + case OperatorType.EQ: + convertedOperatorType = '='; + break; + case OperatorType.NEQ: + convertedOperatorType = '!='; + break; + case OperatorType.LT: + convertedOperatorType = '<'; + break; + case OperatorType.GT: + convertedOperatorType = '>'; + break; + case OperatorType.LTEQ: + convertedOperatorType = '<='; + break; + case OperatorType.GTEQ: + convertedOperatorType = '>='; + break; + default: + convertedOperatorType = key; + } + return { + label: convertedOperatorType, value: convertedOperatorType, + }; +}); + +export const ConstraintLogicTypeAsSelectOptionsList: any[] = Object.keys(ConstraintLogicType).map(key => { + return { + label: key, value: key, + }; +}); diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-editor.component.html b/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-editor.component.html new file mode 100644 index 0000000000..a1d3ce7c32 --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-editor.component.html @@ -0,0 +1,247 @@ + + +

{{ "pageAdmin.policyManagement.policy"| i18n }} {{ "pageAdmin.policyManagement." + viewMode | i18n }}

+
+
+ +
+
+ +
+ arrow_back + {{ 'actions.goBack' | i18n }} +
+
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+ + +
+ +
+ +
+

{{ 'pageAdmin.policyManagement.policyConstraints' | i18n }}

+ +
+ +
+

{{ 'pageAdmin.policyManagement.constraints' | i18n }} {{ '(' + constraints.length + ')' }}

+
+ +
+ +
+
+
+ {{ "pageAdmin.policyManagement.logic" | i18n }} +
+ {{ "pageAdmin.policyManagement.logicTypeHint" | i18n }} +
+
+ +
+
+
+
+ {{ "pageAdmin.policyManagement.leftOperand" | i18n }} +
+
+ {{ "pageAdmin.policyManagement.operator" | i18n }} +
+
+ {{ "pageAdmin.policyManagement.rightOperand" | i18n }} +
+ {{ "pageAdmin.policyManagement.rightOperandHint" | i18n }} + +
+
+
+
+ + + +
+ + +
+
+ + + +
+
+ + +
+
+
+
+ +
+
+ + +
+
diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-editor.component.scss b/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-editor.component.scss new file mode 100644 index 0000000000..89c5e08721 --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-editor.component.scss @@ -0,0 +1,52 @@ +.file-input { + background-color: #ececec; + border-radius: 5px; + width: 300px; + padding: 8px; +} + +.hinttext { + color: #878787; +} + +.details-container { + width: 50%; +} + +.constraints--header--label { + font-size: 14px; + font-weight: 600; +} + +.constraints--header--sub-label { + font-size: 12px; + font-weight: normal; + font-family: Catena-X Light, sans-serif; + color: #777777; + + &--logicType { + font-size: 12px; + font-weight: normal; + font-family: Catena-X Light, sans-serif; + color: #777777; + width: 200px; + @media(max-width: 1137px) { + height: 54px; + } + } +} + +.constraints--header--container { + width: calc(100% - 192px); +} + +.label-row-view-mode { + width: 100%; +} + +.sub-label { + font-size: 12px; + font-weight: normal; + font-family: Catena-X Light, sans-serif; + color: #777777; +} diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-editor.component.spec.ts b/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-editor.component.spec.ts new file mode 100644 index 0000000000..f96a98f783 --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-editor.component.spec.ts @@ -0,0 +1,256 @@ +import { APP_INITIALIZER } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; +import { ActivatedRoute, convertToParamMap, Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { PoliciesFacade } from '@page/admin/presentation/policy-management/policies/policies.facade'; +import { PoliciesAssembler } from '@page/admin/presentation/policy-management/policies/policy.assembler'; +import { OperatorType, Policy, PolicyAction } from '@page/policies/model/policy.model'; +import { ToastService } from '@shared/components/toasts/toast.service'; +import { ViewMode } from '@shared/model/view.model'; +import { renderComponent } from '@tests/test-render.utils'; +import { I18NEXT_SERVICE, ITranslationService } from 'angular-i18next'; +import { of, Subject } from 'rxjs'; +import { PolicyEditorComponent } from './policy-editor.component'; + +describe('PolicyEditorComponent', () => { + let mockPoliciesFacade: Partial; + let mockToastService: Partial; + let mockRouter: Partial; + let mockRoute: Partial; + let selectedPolicySubject: Subject<{ data: Policy }>; + + const mockPolicy: Policy = { + policyName: 'policy123', + policyId: 'policy123', + createdOn: '2024-01-01T00:00:00Z', + validUntil: '2024-12-31T23:59:59Z', + bpn: 'Test BPN', + permissions: [ + { + action: 'use' as PolicyAction, + constraint: { + and: [ + { + leftOperand: 'left1', + operator: { '@id': OperatorType.EQ }, + operatorTypeResponse: OperatorType.EQ, + 'odrl:rightOperand': 'right1', + }, + ], + or: [ + { + leftOperand: 'left2', + operator: { '@id': OperatorType.NEQ }, + operatorTypeResponse: OperatorType.NEQ, + 'odrl:rightOperand': 'right2', + }, + ], + }, + }, + ], + constraints: [], + }; + + beforeEach(() => { + selectedPolicySubject = new Subject(); + mockPoliciesFacade = { + selectedPolicy$: selectedPolicySubject.asObservable(), + setSelectedPolicyById: jasmine.createSpy(), + createPolicy: jasmine.createSpy().and.returnValue(of({})), + updatePolicy: jasmine.createSpy().and.returnValue(of({})), + setPolicies: jasmine.createSpy(), + }; + mockToastService = { + success: jasmine.createSpy(), + error: jasmine.createSpy(), + }; + mockRouter = { + navigate: jasmine.createSpy(), + url: 'admin/policies/create', + }; + + + mockRoute = { + snapshot: { + paramMap: convertToParamMap({ policyId: '1' }), + url: [], + params: {}, + queryParams: {}, + fragment: null, + data: {}, + outlet: 'primary', + component: PolicyEditorComponent, + root: null, + parent: null, + firstChild: null, + children: [], + pathFromRoot: [], + toString: () => '', + routeConfig: null, + title: '', + queryParamMap: convertToParamMap({}), + }, + }; + }); + + const renderPolicyEditorComponent = () => + renderComponent(PolicyEditorComponent, { + imports: [ RouterTestingModule ], + providers: [ + { provide: PoliciesFacade, useValue: mockPoliciesFacade }, + { provide: ToastService, useValue: mockToastService }, + { provide: Router, useValue: mockRouter }, + { provide: ActivatedRoute, useValue: mockRoute }, + FormBuilder, + { + provide: APP_INITIALIZER, + useFactory: (i18next: ITranslationService) => { + return () => + i18next.init({ + lng: 'en', + supportedLngs: [ 'en', 'de' ], + resources: {}, + }); + }, + deps: [ I18NEXT_SERVICE ], + multi: true, + }, + ], + }); + + it('should create', async () => { + const { fixture } = await renderPolicyEditorComponent(); + const { componentInstance } = fixture; + expect(componentInstance).toBeTruthy(); + }); + + it('should initialize the form in create mode', async () => { + const { fixture } = await renderPolicyEditorComponent(); + const { componentInstance } = fixture; + expect(componentInstance.viewMode).toBe(ViewMode.CREATE); + expect(componentInstance.policyForm).toBeTruthy(); + expect(componentInstance.policyForm.get('policyName').valid).toBeFalsy(); // Validators required + expect(componentInstance.policyForm.get('bpns').valid).toBeFalsy(); // Validators required + expect(componentInstance.policyForm.get('validUntil').valid).toBeFalsy(); // Validators required + }); + + it('should initialize the view mode correctly', async () => { + const { fixture } = await renderPolicyEditorComponent(); + const { componentInstance } = fixture; + Object.defineProperty(mockRouter, 'url', { + get: jasmine.createSpy().and.returnValue('admin/policies/create'), + }); + expect(componentInstance.initializeViewMode()).toBe(ViewMode.CREATE); + Object.defineProperty(mockRouter, 'url', { + get: jasmine.createSpy().and.returnValue('admin/policies/edit/1'), + }); + + expect(componentInstance.initializeViewMode()).toBe(ViewMode.EDIT); + + Object.defineProperty(mockRouter, 'url', { + get: jasmine.createSpy().and.returnValue('admin/policies/1'), + }); + + expect(componentInstance.initializeViewMode()).toBe(ViewMode.VIEW); + }); + + it('should add and remove constraints', async () => { + const { fixture } = await renderPolicyEditorComponent(); + const { componentInstance } = fixture; + expect(componentInstance.constraints.length).toBe(1); + + componentInstance.addConstraintFormGroup(); + expect(componentInstance.constraints.length).toBe(2); + + componentInstance.removeConstraintFormGroup(0); + expect(componentInstance.constraints.length).toBe(1); + }); + + it('should move constraints up and down', async () => { + const { fixture } = await renderPolicyEditorComponent(); + const { componentInstance } = fixture; + componentInstance.addConstraintFormGroup(); + componentInstance.addConstraintFormGroup(); + + componentInstance.constraints.at(0).get('leftOperand').setValue('constraint 1'); + componentInstance.constraints.at(1).get('leftOperand').setValue('constraint 2'); + + componentInstance.moveConstraintDown(0); + expect(componentInstance.constraints.at(0).get('leftOperand').value).toBe('constraint 2'); + expect(componentInstance.constraints.at(1).get('leftOperand').value).toBe('constraint 1'); + + componentInstance.moveConstraintUp(1); + expect(componentInstance.constraints.at(0).get('leftOperand').value).toBe('constraint 1'); + expect(componentInstance.constraints.at(1).get('leftOperand').value).toBe('constraint 2'); + }); + + it('should navigate back', async () => { + const { fixture } = await renderPolicyEditorComponent(); + const { componentInstance } = fixture; + componentInstance.navigateBack(); + expect(mockRouter.navigate).toHaveBeenCalledWith([ 'admin/policies' ]); + }); + + it('should save policy in create mode', async () => { + const { fixture } = await renderPolicyEditorComponent(); + const { componentInstance } = fixture; + componentInstance.policyForm.patchValue({ + policyName: 'Test Policy', + validUntil: new Date().toISOString(), + bpns: 'BPN0001', + accessType: 'access', + constraintLogicType: 'AND', + }); + componentInstance.addConstraintFormGroup(); + componentInstance.constraints.at(0).patchValue({ + leftOperand: 'leftOperand', + operator: '=', + rightOperand: 'rightOperand', + }); + + componentInstance.savePolicy(); + expect(mockPoliciesFacade.createPolicy).toHaveBeenCalled(); + expect(mockToastService.success).toHaveBeenCalled(); + expect(mockRouter.navigate).toHaveBeenCalled(); + }); + + it('should update policy form correctly in create mode', async () => { + const { fixture } = await renderPolicyEditorComponent(); + const { componentInstance } = fixture; + componentInstance.selectedPolicy = mockPolicy; + const policy: Policy = PoliciesAssembler.assemblePolicy(mockPolicy); + + componentInstance.viewMode = ViewMode.CREATE; + componentInstance.policyForm = componentInstance.fb.group({ + policyName: '', + validUntil: null, + bpns: '', + accessType: '', + constraintLogicType: '', + constraints: componentInstance.fb.array([]), + }); + + componentInstance.updatePolicyForm(policy); + + // Assert the form values are updated correctly + expect(componentInstance.policyForm.getRawValue().policyName).toBe('policy123'); + expect(componentInstance.policyForm.getRawValue().validUntil).toBe(policy.validUntil); + expect(componentInstance.policyForm.getRawValue().bpns).toBe('Test BPN'); + expect(componentInstance.policyForm.getRawValue().accessType).toBe('USE'); + expect(componentInstance.policyForm.getRawValue().constraintLogicType).toBe('AND'); + expect(componentInstance.policyForm.getRawValue().constraints.length).toBe(1); + + componentInstance.viewMode = ViewMode.VIEW; + + componentInstance.updatePolicyForm(policy); + expect(componentInstance.policyForm.disabled).toBe(true); + + componentInstance.viewMode = ViewMode.EDIT; + componentInstance.updatePolicyForm(policy); + expect(componentInstance.policyForm.get('validUntil').disabled).toBe(false); + expect(componentInstance.policyForm.get('bpns').disabled).toBe(false); + expect(componentInstance.constraints.disabled).toBe(true); + + }); + +}); diff --git a/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-editor.component.ts b/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-editor.component.ts new file mode 100644 index 0000000000..64a036ab06 --- /dev/null +++ b/frontend/src/app/modules/page/admin/presentation/policy-management/policy-editor/policy-editor.component.ts @@ -0,0 +1,325 @@ +import { Component } from '@angular/core'; +import { FormArray, FormBuilder, FormControl, FormGroup, ValidationErrors, Validators } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { bpnListRegex, bpnRegex } from '@page/admin/presentation/bpn-configuration/bpn-configuration.component'; +import { PoliciesFacade } from '@page/admin/presentation/policy-management/policies/policies.facade'; +import { + ConstraintLogicTypeAsSelectOptionsList, + OperatorTypesAsSelectOptionsList, +} from '@page/admin/presentation/policy-management/policy-editor/policy-data'; +import { + ConstraintLogicType, + getOperatorType, + getOperatorTypeSign, + OperatorType, + Policy, + PolicyAction, + PolicyConstraint, + PolicyEntry, +} from '@page/policies/model/policy.model'; +import { BaseInputHelper } from '@shared/abstraction/baseInput/baseInput.helper'; +import { ToastService } from '@shared/components/toasts/toast.service'; +import { ViewMode } from '@shared/model/view.model'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'app-policy-editor', + templateUrl: './policy-editor.component.html', + styleUrls: [ './policy-editor.component.scss' ], +}) +export class PolicyEditorComponent { + + selectedPolicy: Policy; + selectedPolicySubscription: Subscription; + viewMode: ViewMode; + + templateFile: File | null = null; + templateFileName: string = ''; + policyForm: FormGroup; + minDate: Date = new Date(); + templateError: string = ''; + + constructor(private router: Router, private route: ActivatedRoute, public policyFacade: PoliciesFacade, public fb: FormBuilder, private toastService: ToastService) { + } + + get constraints() { + return this.policyForm.get('constraints') as FormArray; + } + + ngOnInit() { + this.viewMode = this.initializeViewMode(); + + + + this.policyForm = this.fb.group({ + policyName: new FormControl('', [ Validators.required, Validators.minLength(8), Validators.maxLength(40) ]), + validUntil: new FormControl('', [ Validators.required, this.futureDateValidator ]), + bpns: new FormControl('', [ Validators.required, this.viewMode === ViewMode.CREATE ? BaseInputHelper.getCustomPatternValidator(bpnRegex, 'bpn') : BaseInputHelper.getCustomPatternValidator(bpnListRegex, 'bpn') ]), + accessType: new FormControl(PolicyAction.ACCESS), + constraints: this.fb.array([]), + constraintLogicType: new FormControl(ConstraintLogicType.AND), + }); + + if (this.viewMode !== ViewMode.CREATE) { + this.setSelectedPolicy(); + this.selectedPolicySubscription = this.policyFacade.selectedPolicy$.subscribe(next => { + this.selectedPolicy = next?.data; + if (next?.data) { + console.log(next.data); + this.updatePolicyForm(this.selectedPolicy); + } + }); + + } else { + this.addConstraintFormGroup(); + } + + } + + initializeViewMode(): ViewMode { + const url = this.router.url; + if (url.includes('create')) { + return ViewMode.CREATE; + } else if (url.includes('edit')) { + return ViewMode.EDIT; + } else { + return ViewMode.VIEW; + } + } + + private setSelectedPolicy(): void { + this.policyFacade.setSelectedPolicyById(this.route.snapshot.paramMap.get('policyId')); + } + + addConstraintFormGroup() { + this.constraints.push(this.fb.group({ + leftOperand: new FormControl('', [ Validators.required ]), + operator: new FormControl('='), + rightOperand: new FormControl('', [ Validators.required ]), + })); + } + + removeConstraintFormGroup(index: number) { + this.constraints.removeAt(index); + } + + moveConstraintUp(index: number): void { + if (index <= 0) { + return; + } + const constraints = this.policyForm.get('constraints') as FormArray; + const constraint = constraints.at(index); + constraints.removeAt(index); + constraints.insert(index - 1, constraint); + } + + moveConstraintDown(index: number): void { + const constraints = this.policyForm.get('constraints') as FormArray; + if (index >= constraints.length - 1) { + return; + } + const constraint = constraints.at(index); + constraints.removeAt(index); + constraints.insert(index + 1, constraint); + } + + + navigateBack() { + this.router.navigate([ 'admin/policies' ]); + } + + savePolicy() { + const policyEntry = this.mapPolicyFormToPolicyEntry(); + const request = this.viewMode === ViewMode.EDIT ? this.policyFacade.updatePolicy(policyEntry) : this.policyFacade.createPolicy(policyEntry); + request.subscribe({ + next: () => { + this.policyFacade.setPolicies(); + this.toastService.success('pageAdmin.policyManagement.successMessage'); + this.router.navigate([ 'admin', 'policies', policyEntry.payload.policy.policyId ]); + }, + error: () => this.toastService.error('pageAdmin.policyManagement.errorMessage'), + }); + } + + /** + * This Feature is commented out for now, because uploading/downloading Templates/Policies is + * currently not a requirement but could be one in the future. + */ + /* + + onFileSelected(event: Event) { + const input = event.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + this.templateFile = input.files[0]; + this.templateFileName = this.templateFile.name; + this.templateError = ''; + } + } + + */ + + navigateToEditView() { + this.router.navigate([ 'admin/policies/', 'edit', this.selectedPolicy.policyId ]); + } + + /** + * This Feature is commented out for now, because uploading/downloading Templates/Policies is + * currently not a requirement but could be one in the future. + */ + /* + downloadTemplateAsJsonFile() { + const policy = this.mapPolicyFormToPolicyEntry(); + const data = JSON.stringify(policy, null, 2); + const blob = new Blob([ data ], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = policy.payload.policy.policyId.length ? 'policy-template-' + policy.payload.policy.policyId : 'policy-template'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + } + + + + applyTemplate() { + if (!this.templateFile) { + return; + } + const reader = new FileReader(); + + reader.onload = () => { + const fileContent = reader.result; + if (typeof fileContent === 'string') { + if (!PoliciesAssembler.validatePoliciesTemplate(JSON.parse(fileContent))) { + this.templateError = 'pageAdmin.policyManagement.templateErrorMessage'; + return; + } + let policyEntry = PoliciesAssembler.mapToPolicyEntryList(JSON.parse(fileContent)); + let policy = PoliciesAssembler.assemblePolicy(policyEntry[0].payload.policy); + this.toastService.success('pageAdmin.policyManagement.changeSuccessMessage'); + this.updatePolicyForm(policy); + } + }; + + reader.onerror = () => { + this.toastService.error(reader.error?.message); + }; + + reader.readAsText(this.templateFile); + + } + */ + updatePolicyForm(policy: Policy) { + + const isFromTemplate = !policy?.permissions[0]?.constraints; + this.policyForm.patchValue({ + policyName: policy?.policyName, + validUntil: policy?.validUntil, + bpns: policy?.bpn ?? policy?.businessPartnerNumber, + accessType: policy?.accessType, + constraintLogicType: policy?.permissions[0]?.constraints?.and?.length ? ConstraintLogicType.AND : ConstraintLogicType.OR, + }); + if (isFromTemplate) { + this.policyForm.patchValue({ constraintLogicType: policy?.permissions[0]?.constraint?.and?.length ? ConstraintLogicType.AND : ConstraintLogicType.OR }); + } + + let permissionList = policy?.permissions[0]?.constraints?.and?.length ? policy?.permissions[0]?.constraints?.and : policy?.permissions[0]?.constraints?.or; + if (!permissionList) { + permissionList = policy?.permissions[0]?.constraint?.and?.length ? policy?.permissions[0]?.constraint?.and : policy?.permissions[0]?.constraint?.or; + } + + let constraintsList = permissionList.map((constraint) => this.fb.group({ + leftOperand: this.fb.control(constraint.leftOperand, [ Validators.required ]), + operator: this.fb.control(constraint?.operator?.['@id'] ? getOperatorTypeSign(OperatorType[constraint?.operator?.['@id'].toUpperCase()]) : getOperatorTypeSign(constraint?.operatorTypeResponse)), + rightOperand: this.fb.control(constraint['odrl:rightOperand'] ?? constraint.rightOperand, [ Validators.required ]), + })); + + + this.policyForm.setControl('constraints', this.fb.array(constraintsList)); + + + if (this.viewMode === ViewMode.VIEW) { + this.policyForm.disable(); + } + + if (this.viewMode === ViewMode.EDIT) { + this.policyForm.disable(); + this.constraints.controls.forEach(control => { + control.disable(); + }); + this.policyForm.get('validUntil').enable(); + this.policyForm.get('bpns').enable(); + } + + } + + mapPolicyFormToPolicyEntry(): PolicyEntry { + let policyEntry: PolicyEntry; + let policyConstraints: PolicyConstraint[]; + + policyConstraints = this.policyForm.get('constraints').getRawValue().map((constraint) => { + return { + 'odrl:leftOperand': constraint.leftOperand, + 'odrl:operator': { + '@id': 'odrl:' + getOperatorType(constraint.operator).toLowerCase(), + }, + 'odrl:rightOperand': constraint.rightOperand, + }; + }); + + policyEntry = { + validUntil: this.policyForm.get('validUntil').getRawValue() + ':00.000000000Z', + businessPartnerNumber: this.viewMode === ViewMode.CREATE ? this.policyForm.get('bpns').getRawValue() : this.policyForm.get('bpns').getRawValue()?.trim()?.split(','), + payload: { + '@context': { + odrl: 'http://www.w3.org/ns/odrl/2/', + }, + '@id': this.policyForm.get('policyName').getRawValue(), + policy: { + policyId: this.policyForm.get('policyName').getRawValue(), + createdOn: new Date(Date.now()).toISOString().replace('Z', '000000Z'), + validUntil: this.policyForm.get('validUntil').getRawValue() + ':00.000000000Z', + permissions: [ + { + action: this.policyForm.get('accessType').getRawValue().toLowerCase(), + constraint: { + and: this.policyForm.get('constraintLogicType').getRawValue() === ConstraintLogicType.AND ? policyConstraints : null, + or: this.policyForm.get('constraintLogicType').getRawValue() === ConstraintLogicType.OR ? policyConstraints : null, + }, + }, + ], + }, + }, + }; + + if (policyEntry.payload.policy.permissions[0].constraint?.and?.length) { + delete policyEntry.payload.policy.permissions[0].constraint?.or; + } else { + delete policyEntry.payload.policy.permissions[0].constraint?.and; + } + + return policyEntry; + } + + private futureDateValidator = (control: FormControl): ValidationErrors | null => { + if (!control.value) { + return null; + } + + const currentDate = new Date(); + const inputDate = new Date(control.value); + if (inputDate < currentDate) { + return { pastDate: true }; + } + return null; + }; + + + protected readonly ViewMode = ViewMode; + protected readonly OperatorTypesAsSelectOptionsList = OperatorTypesAsSelectOptionsList; + protected readonly ConstraintLogicTypeAsSelectOptionsList = ConstraintLogicTypeAsSelectOptionsList; +} + diff --git a/frontend/src/app/modules/page/policies/model/policy.model.ts b/frontend/src/app/modules/page/policies/model/policy.model.ts index 1d68e5e0f1..f173ced62b 100644 --- a/frontend/src/app/modules/page/policies/model/policy.model.ts +++ b/frontend/src/app/modules/page/policies/model/policy.model.ts @@ -1,3 +1,5 @@ +import { CalendarDateModel } from '@core/model/calendar-date.model'; + /******************************************************************************** * Copyright (c) 2022, 2023, 2024 Contributors to the Eclipse Foundation * @@ -16,43 +18,128 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -// TODO: Decide if long term a Policy state, facade, ResponseType and Assembler is needed -// TODO: Align with BE to a first valid Policy Model, changes seem to be happen frequently +// RESPONSE + +export interface PolicyResponseMap { + [key: string]: PolicyEntry[]; +} + +export interface PolicyEntry { + validUntil: string; + payload: PolicyPayload; + businessPartnerNumber?: string[] | string; + policyIds?: string[]; +} + +export interface PolicyPayload { + '@context': { + odrl: string; + }; + '@id': string; + policy: Policy; +} + + export interface Policy { + // props in response policyId: string; - createdOn: string; - validUntil: string; - permissions?: PolicyPermission[]; + createdOn: CalendarDateModel | string | number; + validUntil: CalendarDateModel | string | number; + permissions: PolicyPermission[]; + + // additional props + policyName?: string; + bpn?: string; + constraints?: string[] + accessType?: PolicyAction, + businessPartnerNumber?: string | string[] + } export interface PolicyPermission { - action: PolicyType; - constraints?: Constraint[]; + action: PolicyAction; + constraint?: { + and: PolicyConstraint[]; + or: null | PolicyConstraint[]; + xone?: PolicyConstraint[]; + andsequence?: PolicyConstraint[]; + }; + constraints?: { + and: PolicyConstraint[]; + or: null | PolicyConstraint[]; + xone?: PolicyConstraint[]; + andsequence?: PolicyConstraint[]; + }; } -export enum PolicyType { - ACCESS="ACCESS", - USE="USE" +export enum PolicyAction { + ACCESS = 'ACCESS', + USE = 'USE' } -export interface Constraint { - leftOperand: string; - operator: OperatorType; - rightOperand: string[]; +export interface PolicyConstraint { + leftOperand?: string; + 'odrl:leftOperand'?: string; + operatorTypeResponse?: OperatorType; + operator?: { '@id': OperatorType }; + 'odrl:operator'?: { '@id': OperatorType }; + rightOperand?: string; + 'odrl:rightOperand'?: string; } export enum OperatorType { - EQ = 'eq', - NEQ = 'neq', - LT = 'lt', - GT = 'gt', - IN = 'in', - LTEQ = 'lteq', - GTEQ = 'gteq', - ISA = 'isA', - HASPART = 'hasPart', - ISPARTOF = 'isPartOf', - ISONEOF = 'isOneOf', - ISALLOF = 'isAllOf', - ISNONEOF = 'isNoneOf', + EQ = 'EQ', + NEQ = 'NEQ', + LT = 'LT', + GT = 'GT', + LTEQ = 'LTEQ', + GTEQ = 'GTEQ', + IN = 'IN', + ISA = 'ISA', + HASPART = 'HASPART', + ISPARTOF = 'ISPARTOF', + ISONEOF = 'ISONEOF', + ISALLOF = 'ISALLOF', + ISNONEOF = 'ISNONEOF', +} + +const OperatorSignsToTypes: { [key: string]: OperatorType } = { + '=': OperatorType.EQ, + '!=': OperatorType.NEQ, + '<': OperatorType.LT, + '>': OperatorType.GT, + '<=': OperatorType.LTEQ, + '>=': OperatorType.GTEQ, + ...OperatorType, + // Add more mappings as needed +}; + +export function getOperatorType(sign: string): OperatorType | undefined { + return OperatorSignsToTypes[sign]; +} + +export function getOperatorTypeSign(type: OperatorType): string { + switch (type) { + case OperatorType.EQ: + return '='; + case OperatorType.NEQ: + return '!='; + case OperatorType.LT: + return '<'; + case OperatorType.GT: + return '>'; + case OperatorType.LTEQ: + return '<='; + case OperatorType.GTEQ: + return '>='; + default: + return type.toString(); + } +} + +export enum ConstraintLogicType { + AND = 'AND', + OR = 'OR', + XONE = 'XONE', + ANDSEQUENCE = 'ANDSEQUENCE' } diff --git a/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.spec.ts b/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.spec.ts index 179dec1717..cdf60e65d5 100644 --- a/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.spec.ts +++ b/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.spec.ts @@ -27,7 +27,7 @@ describe('AssetPublisherComponent', () => { const { fixture } = await renderAssetPublisherComponent(); const { componentInstance } = fixture; - const dummyPolicy: Policy = { policyId: 'id-1', createdOn: 'testdate', validUntil: 'testdate' }; + const dummyPolicy: Policy = { policyId: 'id-1', createdOn: 'testdate', validUntil: 'testdate', permissions: [] }; policyServiceSpy.publishAssets.and.returnValue(of({})); policyServiceSpy.getPolicies.and.returnValue(of([dummyPolicy])); @@ -49,7 +49,7 @@ describe('AssetPublisherComponent', () => { it('should set policies when requesting policies', async function() { const { fixture } = await renderAssetPublisherComponent(); const { componentInstance } = fixture; - const dummyPolicy: Policy = { policyId: 'id-1', createdOn: 'testdate', validUntil: 'testdate' }; + const dummyPolicy: Policy = { policyId: 'id-1', createdOn: 'testdate', validUntil: 'testdate', permissions: [] }; const submittedSpy = spyOn(componentInstance.submitted, 'emit'); diff --git a/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.ts b/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.ts index b5ac0d0ee1..0d6f9ffbbb 100644 --- a/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.ts +++ b/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.ts @@ -1,9 +1,11 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { FormControl, Validators } from '@angular/forms'; +import { PoliciesAssembler } from '@page/admin/presentation/policy-management/policies/policy.assembler'; import { ImportState, Part } from '@page/parts/model/parts.model'; import { Policy } from '@page/policies/model/policy.model'; import { PolicyService } from '@shared/service/policy.service'; import { Observable, Subscription } from 'rxjs'; +import { map } from 'rxjs/operators'; @Component({ selector: 'app-asset-publisher', @@ -55,7 +57,9 @@ export class AssetPublisherComponent { } private getPolicies() { - this.policiesSubscription = this.policyService.getPolicies().subscribe(data => { + this.policiesSubscription = this.policyService.getPolicies().pipe(map(response => { + return PoliciesAssembler.mapToPolicyEntryList(response).map(entry => entry.payload.policy).map(policy => PoliciesAssembler.assemblePolicy(policy)); + })).subscribe(data => { this.policiesList = data; }) } diff --git a/frontend/src/app/modules/shared/components/multi-select-autocomplete/table-type.model.ts b/frontend/src/app/modules/shared/components/multi-select-autocomplete/table-type.model.ts index 27a8a5a6d7..c2c495de62 100644 --- a/frontend/src/app/modules/shared/components/multi-select-autocomplete/table-type.model.ts +++ b/frontend/src/app/modules/shared/components/multi-select-autocomplete/table-type.model.ts @@ -22,7 +22,8 @@ export enum TableType { AS_PLANNED_OWN = 'AS_PLANNED_OWN', RECEIVED_NOTIFICATION = 'RECEIVED_NOTIFICATION', SENT_NOTIFICATION = 'SENT_NOTIFICATION', - CONTRACTS='CONTRACTS' + CONTRACTS = 'CONTRACTS', + POLICIES = 'POLICIES' } export enum NotificationChannel { diff --git a/frontend/src/app/modules/shared/components/parts-table/policies-configuration.model.ts b/frontend/src/app/modules/shared/components/parts-table/policies-configuration.model.ts new file mode 100644 index 0000000000..5dc0412b82 --- /dev/null +++ b/frontend/src/app/modules/shared/components/parts-table/policies-configuration.model.ts @@ -0,0 +1,21 @@ +import { TableFilterConfiguration } from '@shared/components/parts-table/parts-config.model'; + +export class PoliciesConfigurationModel extends TableFilterConfiguration { + constructor() { + const sortableColumns = { + select: false, + bpn: false, + policyName: false, + policyId: false, + accessType: false, + createdOn: false, + validUntil: false, + constraints: false, + menu: false, + }; + + const dateFields = [ 'createdOn', 'validUntil' ]; + const singleSearchFields = []; + super(sortableColumns, dateFields, singleSearchFields, true); + } +} diff --git a/frontend/src/app/modules/shared/components/table/table.component.html b/frontend/src/app/modules/shared/components/table/table.component.html index 841eea23b6..35b7b88f41 100644 --- a/frontend/src/app/modules/shared/components/table/table.component.html +++ b/frontend/src/app/modules/shared/components/table/table.component.html @@ -21,14 +21,17 @@
-
{{ tableHeader | i18n }} +
{{ tableHeader | i18n }} settings
-
+
+
+ + +
+ +
+
+ + +
+ [ngClass]="{'rows-dashboard': labelId?.startsWith('dashboard'), 'rows-policy': labelId?.startsWith('policies')} ">

{{ tableHeader | i18n }}

-
+

{{ selectedPartsInfoLabel | i18n : {count: selection?.selected?.length || 0} }}

- @@ -367,11 +405,14 @@

{{ 'table.noResultFound' | i18n }}

- + + {{ (element[column] | date:'yyyy-MM-dd HH:mm') }} + diff --git a/frontend/src/app/modules/shared/components/table/table.component.scss b/frontend/src/app/modules/shared/components/table/table.component.scss index 7579ffee22..47d3977286 100644 --- a/frontend/src/app/modules/shared/components/table/table.component.scss +++ b/frontend/src/app/modules/shared/components/table/table.component.scss @@ -255,3 +255,14 @@ tr.error { visibility: hidden; } +.policy-table-header { + font-size: 24px; + font-family: Catena-X SemiBold, sans-serif; + text-transform: uppercase; + color: rgb(17, 17, 17); +} + +.rows-policy { + height: 63vh !important; +} + diff --git a/frontend/src/app/modules/shared/components/table/table.component.ts b/frontend/src/app/modules/shared/components/table/table.component.ts index 5395d40b7b..bbfa45f682 100644 --- a/frontend/src/app/modules/shared/components/table/table.component.ts +++ b/frontend/src/app/modules/shared/components/table/table.component.ts @@ -43,6 +43,7 @@ import { TableHeaderSort, } from '@shared/components/table/table.model'; import { ToastService } from '@shared/components/toasts/toast.service'; +import { isDateFilter } from '@shared/helper/filter-helper'; import { addSelectedValues, clearAllRows, clearCurrentRows, removeSelectedValues } from '@shared/helper/table-helper'; import { NotificationStatus } from '@shared/model/notification.model'; import { FlattenObjectPipe } from '@shared/pipes/flatten-object.pipe'; @@ -60,6 +61,7 @@ export class TableComponent { @ViewChild('tableElement', { read: ElementRef }) tableElementRef: ElementRef; @Input() additionalTableHeader = false; @Input() tableHeaderMenuEnabled = false; + @Input() basicTableHeaderMenuEnabled = false; @Input() set tableConfig(tableConfig: TableConfig) { @@ -146,6 +148,7 @@ export class TableComponent { @Output() multiSelect = new EventEmitter(); @Output() clickSelectAction = new EventEmitter(); @Output() filterActivated = new EventEmitter(); + @Output() deletionClicked = new EventEmitter(); @Input() public autocompleteEnabled = false; @Input() tableSettingsEnabled: boolean = false; @@ -380,6 +383,10 @@ export class TableComponent { this.router.navigate([ 'inbox/create' ]); } + navigateToCreationPath() { + this.router.navigate([ this.router.url, 'create' ]); + } + private menuActionsWithAddedDefaultActions(menuActionsConfig: MenuActionConfig[] = []): MenuActionConfig[] { const viewDetailsMenuAction: MenuActionConfig = { label: 'actions.viewDetails', @@ -400,6 +407,13 @@ export class TableComponent { return [ ...defaultActionsToAdd, ...menuActionsConfig ]; }; - protected readonly MainAspectType = MainAspectType; + handleItemDeletion() { + this.deletionClicked.emit(); + } + public isDateElement(key: string) { + return isDateFilter(key); + } + + protected readonly MainAspectType = MainAspectType; } diff --git a/frontend/src/app/modules/shared/helper/filter-helper.ts b/frontend/src/app/modules/shared/helper/filter-helper.ts index 41b64b1b85..76956403f2 100644 --- a/frontend/src/app/modules/shared/helper/filter-helper.ts +++ b/frontend/src/app/modules/shared/helper/filter-helper.ts @@ -26,10 +26,10 @@ import { import { NotificationDeeplinkFilter } from '@shared/model/notification.model'; -export const DATE_FILTER_KEYS = [ 'manufacturingDate', 'functionValidFrom', 'functionValidUntil', 'validityPeriodFrom', 'validityPeriodTo', 'createdDate', 'targetDate', 'creationDate', 'endDate' ]; +export const DATE_FILTER_KEYS = [ 'manufacturingDate', 'functionValidFrom', 'functionValidUntil', 'validityPeriodFrom', 'validityPeriodTo', 'createdDate', 'targetDate', 'creationDate', 'endDate', 'createdOn', 'validUntil' ]; // TODO: Refactor function -export function enrichFilterAndGetUpdatedParams(filter: AssetAsBuiltFilter, params: HttpParams, filterOperator: string): HttpParams { +export function enrichFilterAndGetUpdatedParams(filter: AssetAsBuiltFilter | any, params: HttpParams, filterOperator: string): HttpParams { for (const key in filter) { let operator: string; diff --git a/frontend/src/app/modules/shared/helper/table-helper.ts b/frontend/src/app/modules/shared/helper/table-helper.ts index 7600edb4d8..8134a2e9e2 100644 --- a/frontend/src/app/modules/shared/helper/table-helper.ts +++ b/frontend/src/app/modules/shared/helper/table-helper.ts @@ -36,9 +36,9 @@ export function clearAllRows(selection: any, multiSelect: any): void { } export function clearCurrentRows(selection: any, dataSourceData: unknown[], multiSelect: any): void { - this.removeSelectedValues(selection, dataSourceData); + removeSelectedValues(selection, dataSourceData); - multiSelect.emit(this.selection.selected); + multiSelect.emit([]); } export function setMultiSorting( event: KeyboardEvent): boolean { diff --git a/frontend/src/app/modules/shared/model/view.model.ts b/frontend/src/app/modules/shared/model/view.model.ts index 8ba9bf0a91..9674fd67ba 100644 --- a/frontend/src/app/modules/shared/model/view.model.ts +++ b/frontend/src/app/modules/shared/model/view.model.ts @@ -40,3 +40,9 @@ export class View implements OptionalViewData, OptionalViewError, Optional loader?: boolean; error?: Error; } + +export enum ViewMode { + VIEW = 'view', + EDIT = 'edit', + CREATE = 'create' +} diff --git a/frontend/src/app/modules/shared/pipes/error-message.pipe.ts b/frontend/src/app/modules/shared/pipes/error-message.pipe.ts index 041c627c35..a84659e966 100644 --- a/frontend/src/app/modules/shared/pipes/error-message.pipe.ts +++ b/frontend/src/app/modules/shared/pipes/error-message.pipe.ts @@ -81,7 +81,9 @@ export class ErrorMessagePipe implements PipeTransform { [ 'bpn', _ => getErrorMapping('bpn') ], [ 'invalidBpn', _ => getErrorMapping('invalidBpn' ) ], [ 'email', _ => getErrorMapping('email') ], + [ 'pastDate', _ => getErrorMapping('pastDate') ], [ 'generic', _ => getErrorMapping('generic') ], + [ 'minimumOneConstraint', _ => getErrorMapping('minimumOneConstraint') ], ]); const keys = Object.keys(errors); diff --git a/frontend/src/app/modules/shared/service/policy.service.spec.ts b/frontend/src/app/modules/shared/service/policy.service.spec.ts index c91603f210..7b8c3f27d3 100644 --- a/frontend/src/app/modules/shared/service/policy.service.spec.ts +++ b/frontend/src/app/modules/shared/service/policy.service.spec.ts @@ -2,30 +2,241 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/ import { TestBed } from '@angular/core/testing'; import { ApiService } from '@core/api/api.service'; import { AuthService } from '@core/auth/auth.service'; +import { environment } from '@env'; +import { OperatorType, Policy, PolicyAction, PolicyEntry, PolicyResponseMap } from '@page/policies/model/policy.model'; import { KeycloakService } from 'keycloak-angular'; - +import { MockPolicyResponseMap } from '../../../mocks/services/policy-mock/policy.model'; import { PolicyService } from './policy.service'; -describe('AssetPublisherService', () => { +describe('PolicyService', () => { let service: PolicyService; - let httpTestingController: HttpTestingController; + let httpMock: HttpTestingController; + let apiUrl: string; let authService: AuthService; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [PolicyService, ApiService, KeycloakService, AuthService], + imports: [ HttpClientTestingModule ], + providers: [ PolicyService, ApiService, KeycloakService, AuthService ], }); service = TestBed.inject(PolicyService); - httpTestingController = TestBed.inject(HttpTestingController); + httpMock = TestBed.inject(HttpTestingController); + apiUrl = environment.apiUrl; authService = TestBed.inject(AuthService); }); afterEach(() => { - httpTestingController.verify(); + httpMock.verify(); }); it('should be created', () => { expect(service).toBeTruthy(); }); + + it('should get policies', () => { + spyOn(authService, 'getBearerToken').and.returnValue('your_mocked_token'); + + const mockResponse: PolicyResponseMap = MockPolicyResponseMap; + + service.getPolicies().subscribe(response => { + expect(response).toEqual(Object(mockResponse)); + }); + + const req = httpMock.expectOne(`${ apiUrl }/policies`); + expect(req.request.method).toEqual('GET'); + req.flush(mockResponse); + }); + + it('should get policy by id', () => { + spyOn(authService, 'getBearerToken').and.returnValue('your_mocked_token'); + + const policyId = '123'; + const mockPolicy: Policy = { + policyId: 'policy123', + createdOn: '2024-01-01T00:00:00Z', + validUntil: '2024-12-31T23:59:59Z', + permissions: [ + { + action: 'use' as PolicyAction, + constraint: { + and: [ + { + leftOperand: 'left1', + operator: { '@id': OperatorType.EQ }, + operatorTypeResponse: OperatorType.EQ, + 'odrl:rightOperand': 'right1', + }, + ], + or: [ + { + leftOperand: 'left2', + operator: { '@id': OperatorType.NEQ }, + operatorTypeResponse: OperatorType.NEQ, + 'odrl:rightOperand': 'right2', + }, + ], + }, + }, + ], + }; + const policyEntry: PolicyEntry = { + payload: { + '@context': { + odrl: 'test', + }, + '@id': 'entry123', + policy: mockPolicy, + }, + validUntil: '2024-01-01T00:00:00Z', + }; + + service.getPolicyById(policyId).subscribe(response => { + expect(response).toEqual(Object(mockPolicy)); + }); + + const req = httpMock.expectOne(`${ apiUrl }/policies/${ policyId }`); + expect(req.request.method).toEqual('GET'); + req.flush(mockPolicy); + }); + + it('should publish assets', () => { + spyOn(authService, 'getBearerToken').and.returnValue('your_mocked_token'); + + const assetIds = [ 'asset1', 'asset2' ]; + const policyId = '123'; + const mockResponse = {}; + + service.publishAssets(assetIds, policyId).subscribe(response => { + expect(response).toEqual(Object(mockResponse)); + }); + + const req = httpMock.expectOne(`${ apiUrl }/assets/publish`); + expect(req.request.method).toEqual('POST'); + const testObject = '{\"assetIds\":[\"asset1\",\"asset2\"],\"policyId\":\"123\"}'; + expect(JSON.parse(JSON.stringify(req.request.body))).toEqual(testObject); + req.flush(mockResponse); + }); + + it('should delete policy', () => { + spyOn(authService, 'getBearerToken').and.returnValue('your_mocked_token'); + + const policyId = '123'; + const mockResponse = {}; + + service.deletePolicy(policyId).subscribe(response => { + expect(response).toEqual(Object(mockResponse)); + }); + + const req = httpMock.expectOne(`${ apiUrl }/policies/${ policyId }`); + expect(req.request.method).toEqual('DELETE'); + req.flush(mockResponse); + }); + + it('should create policy', () => { + spyOn(authService, 'getBearerToken').and.returnValue('your_mocked_token'); + + const policyEntry: PolicyEntry = { + payload: { + '@context': { + odrl: 'test', + }, + '@id': 'entry123', + policy: { + policyId: 'policy123', + createdOn: '2024-01-01T00:00:00Z', + validUntil: '2024-12-31T23:59:59Z', + permissions: [ + { + action: 'use' as PolicyAction, + constraint: { + and: [ + { + leftOperand: 'left1', + operator: { '@id': OperatorType.EQ }, + operatorTypeResponse: OperatorType.EQ, + 'odrl:rightOperand': 'right1', + }, + ], + or: [ + { + leftOperand: 'left2', + operator: { '@id': OperatorType.NEQ }, + operatorTypeResponse: OperatorType.NEQ, + 'odrl:rightOperand': 'right2', + }, + ], + }, + }, + ], + }, + }, + validUntil: '2024-01-01T00:00:00Z', + }; + const mockResponse = {}; + + service.createPolicy(policyEntry).subscribe(response => { + expect(response).toEqual(Object(mockResponse)); + }); + + const req = httpMock.expectOne(`${ apiUrl }/policies`); + expect(req.request.method).toEqual('POST'); + expect(req.request.body).toEqual(JSON.stringify(policyEntry)); + req.flush(mockResponse); + }); + + it('should update policy', () => { + spyOn(authService, 'getBearerToken').and.returnValue('your_mocked_token'); + + const policyEntry: PolicyEntry = { + payload: { + '@context': { + odrl: 'test', + }, + '@id': 'entry123', + policy: { + policyId: 'policy123', + createdOn: '2024-01-01T00:00:00Z', + validUntil: '2024-12-31T23:59:59Z', + permissions: [ + { + action: 'use' as PolicyAction, + constraint: { + and: [ + { + leftOperand: 'left1', + operator: { '@id': OperatorType.EQ }, + operatorTypeResponse: OperatorType.EQ, + 'odrl:rightOperand': 'right1', + }, + ], + or: [ + { + leftOperand: 'left2', + operator: { '@id': OperatorType.NEQ }, + operatorTypeResponse: OperatorType.NEQ, + 'odrl:rightOperand': 'right2', + }, + ], + }, + }, + ], + }, + }, + validUntil: '2024-01-01T00:00:00Z', + }; + const body = { + policyIds: [ policyEntry.payload.policy.policyId ], + validUntil: policyEntry.validUntil, + }; + const mockResponse = {}; + + service.updatePolicy(policyEntry).subscribe(response => { + expect(response).toEqual(Object(mockResponse)); + }); + + const req = httpMock.expectOne(`${ apiUrl }/policies`); + expect(req.request.method).toEqual('PUT'); + expect(req.request.body).toEqual(JSON.stringify(body)); + req.flush(mockResponse); + }); }); diff --git a/frontend/src/app/modules/shared/service/policy.service.ts b/frontend/src/app/modules/shared/service/policy.service.ts index 2154cb5f92..ad0e6dfe5b 100644 --- a/frontend/src/app/modules/shared/service/policy.service.ts +++ b/frontend/src/app/modules/shared/service/policy.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { ApiService } from '@core/api/api.service'; import { environment } from '@env'; -import { Policy } from '@page/policies/model/policy.model'; +import { Policy, PolicyEntry, PolicyResponseMap } from '@page/policies/model/policy.model'; import { Observable } from 'rxjs'; @Injectable({ @@ -11,12 +11,35 @@ export class PolicyService { private readonly url = environment.apiUrl; constructor(private readonly apiService: ApiService) {} - getPolicies(): Observable { - return this.apiService - .getBy(`${this.url}/policies`); + getPolicies(): Observable { + return this.apiService.get(`${ this.url }/policies`); + } + + getPolicyById(policyId: string): Observable { + return this.apiService.getBy(`${ this.url }/policies/${ policyId }`); } publishAssets(assetIds: string[],policyId: string) { return this.apiService.post(`${this.url}/assets/publish`, {assetIds, policyId}); } + + deletePolicy(policyId: string) { + return this.apiService.delete(`${ this.url }/policies/` + policyId); + } + + createPolicy(policyEntry: PolicyEntry): Observable { + return this.apiService.post(`${ this.url }/policies`, policyEntry); + } + + updatePolicy(policyEntry: PolicyEntry) { + policyEntry.policyIds = [ policyEntry.payload.policy.policyId ]; + + const body = { + policyIds: [ policyEntry.payload.policy.policyId ], + validUntil: policyEntry.validUntil, + businessPartnerNumbers: policyEntry.businessPartnerNumber, + }; + + return this.apiService.put(`${ this.url }/policies`, body); + } } diff --git a/frontend/src/assets/locales/de/common.json b/frontend/src/assets/locales/de/common.json index 4916cb9200..3392a0067e 100644 --- a/frontend/src/assets/locales/de/common.json +++ b/frontend/src/assets/locales/de/common.json @@ -12,6 +12,7 @@ "adminRegistry": "Registry-Abfragen", "adminBpn": "BPN - EDC Konfiguration", "adminImport": "Datenbereitstellung", + "adminPolicies" : "Richtlinienverwaltung", "partMismatch": "Die ausgewählten Teile müssen vom selben Eigentümer sein, um ein Qualitätsthema zu erstellen (Eigen, Lieferant)", "unauthorized": "Die Funktion ist aufgrund einer fehlenden Rolle deaktiviert. Bitten Sie Ihren Administrator, die erforderliche Rolle für die Funktion bereitzustellen.", "notAllowedForAsPlanned": "Diese Funktion ist für Produkte im Lebenszyklus \"AsPlanned\" nicht verfügbar.", @@ -66,7 +67,9 @@ "goBack": "Zurück", "publishAssets": "Produkte veröffentlichen", "maximizeTable": "Volle Breite", - "userSettings": "Tabellen Einstellung" + "userSettings" : "Tabellen Einstellung", + "uploadFile" : "Richtlinien JSON-Datei hochladen", + "downloadFile" : "Vorlage als Datei herunterladen" }, "publisher": { "selectedAssets": "Ausgewählte Produkte", @@ -116,10 +119,13 @@ "viewDetails" : "Details anzeigen", "more" : "Mehr Aktionen", "editNotification" : "Qualitätsthemen bearbeiten", - "selectAtLeastOne" : "Aktionen erfordern mindestens eine Selektion in der Tabelle", + "selectAtLeastOne" : "Erfordert mindestens eine Selektion in der Tabelle", + "selectOnlyOne" : "Aktion erfordert eine einzige Selektion in der Tabelle", "unauthorized" : "Die Funktion ist aufgrund einer fehlenden Rolle deaktiviert. Bitten Sie Ihren Administrator, die erforderliche Rolle für die Funktion bereitzustellen.", "noFunctionality" : "Funktionalität wurde noch nicht implementiert.", "onlyNotificationInStatusCreatedAllowed" : "Aktion erfordert eine Selektion an Qualitätsthemen im Status \"Erstellt\".", + "createPolicy" : "Richtlinie erstellen", + "deletePolicy" : "Selektierte Richtlinien löschen", "column": { "id": "ID", "idShort": "Kurz-ID", @@ -162,8 +168,14 @@ "importNote": "Importbemerkung", "importDate": "Importdatum", "type": "Typ", - "title": "Titel" - + "title" : "Titel", + "policyId" : "Policy ID", + "validUntil" : "Gültig bis", + "createdOn" : "Gültig von", + "policyName" : "Richtlinienname", + "bpn" : "BPN Selektion", + "constraints" : "Bedingungen", + "accessType" : "Zugriffsart" } }, "dataLoading": { @@ -207,12 +219,14 @@ "maxLength": "Bitte geben Sie maximal {{maxLength}} Zeichen ein. Momentan: {{current}}", "pattern": "Bitte geben Sie die Daten in folgendem Format ein: {{- pattern}}.", "url": "Bitte geben Sie eine valide URL ein.", - "bpn": "Bitte geben Sie eine valide BPN ein. BPN {{applicationBpn}} ist nicht erlaubt.", + "bpn" : "Bitte geben Sie eine valide BPN ein.", "maxDate": "Bitte wählen Sie ein Datum vor dem {{- maxDate}}.", "minDate": "Bitte wählen Sie ein Datum nach dem {{- minDate}}.", "currentDate": "Bitte wählen Sie ein Datum nach dem {{- currentDate}}.", "generic": "Die Eingabe ist ungültig.", - "invalidBpn": "Die eigene BPN darf nicht verwendet werden." + "invalidBpn" : "Die eigene BPN darf nicht verwendet werden.", + "pastDate" : "Bitte wählen Sie ein Datum in Zukunft aus.", + "minimumOneConstraint" : "Bitte fügen Sie mindestens eine valide Richtlinie hinzu." }, "requestAlert": { "headline": "Qualitätswarnung erstellen", diff --git a/frontend/src/assets/locales/de/page.admin.json b/frontend/src/assets/locales/de/page.admin.json index 6656742029..7616c7517c 100644 --- a/frontend/src/assets/locales/de/page.admin.json +++ b/frontend/src/assets/locales/de/page.admin.json @@ -52,9 +52,50 @@ "selectAtleastOne": "Selektieren Sie mindestens einen Vertrag um exportieren zu können.", "exportAsCSV": ".CSV-Datei Export", "overview": "Übersicht", - "policyDetail": "Richtlinien details", + "policyDetail" : "Vereinbarungsdetails", "jsonViewer": "Json Ansicht", "jsonTreeViewer": "Json-Baumansicht" + }, + "policyManagement" : { + "policyManagement" : "Richtlinienverwaltung", + "deleteSuccess" : "Selektierte Richtlinien wurden erfolgreich gelöscht.", + "deleteError" : "Fehler bei der Löschung der selektierten Richtlinien.", + "confirm" : "Bestätigen", + "policyDeletion" : "Löschen bestätigen", + "deletionText" : "Möchten sie die selektierten Richtlinien löschen?", + "policy" : "Richtlinie", + "create" : "erstellen", + "edit" : "bearbeiten", + "view" : "details", + "selectFileHint" : "Name der selektierten Vorlage", + "applyChange" : "Inhalt der hochgeladenen Datei anwenden und existierende Daten überschreiben", + "chooseFile" : "Selektieren Sie eine Vorlage um Änderungen anzuwenden", + "apply" : "Anwenden", + "policyName" : "Richtlinienname", + "validUntil" : "Gültig bis", + "bpnls" : "BPNs", + "bpn" : "BPN", + "policyConstraints" : "Richtlinientyp", + "accessType" : "Zugriffstyp", + "constraints" : "Regeln", + "logic" : "Logik", + "logicTypeHint" : "Bitte beachten Sie, dass die Reihenfolge der Regeln relevant ist", + "constraintLogicType" : "Logik der Regel", + "leftOperand" : "Linker Operand", + "operator" : "Operator", + "rightOperand" : "Rechter Operand", + "rightOperandHint" : "Bitte geben Sie kommagetrennten Text für die Operatoren IN, ISONEOF, ISALLOF, ISNONEOF an", + "moveDownward" : "Nach unten verschieben", + "moveUpward" : "Nach oben verschieben", + "successMessage" : "Richtlinie wurde erfolgreich gespeichert", + "changeSuccessMessage" : "JSON-Datei erfolgreich angewandt", + "errorMessage" : "Fehler beim Versuch die Richtlinie abzuspeichern", + "templateErrorMessage" : "Die selektierte Vorlagendatei ist nicht konform für Richtlinien. Bitte wählen Sie eine valide .JSON Vorlagendatei aus.", + "invalidForm" : "Bitte füllen Sie die Eingabefelder korrekt aus", + "bpnsHint" : "Liste von BPNs separiert durch ein Kommazeichen", + "bpnHint" : "", + "savePolicy" : "Richtlinie speichern", + "addConstraint" : "Regel hinzufügen" } } } diff --git a/frontend/src/assets/locales/en/common.json b/frontend/src/assets/locales/en/common.json index de01b0d767..3538e19c9f 100644 --- a/frontend/src/assets/locales/en/common.json +++ b/frontend/src/assets/locales/en/common.json @@ -11,6 +11,7 @@ "adminRegistry": "Registry lookups", "adminBpn": "BPN - EDC configuration", "adminImport": "Data provisioning", + "adminPolicies" : "Policy management", "partMismatch": "Selected parts must have same owner to create quality topic (Own, Supplier)", "unauthorized": "Functionality is disabled because of missing role. Ask your administrator to provide the required role for the functionality.", "notAllowedForAsPlanned": "This function is not available for Parts in the Lifecycle \"AsPlanned\".", @@ -64,7 +65,9 @@ "goBack": "Back", "publishAssets": "Publish assets", "maximizeTable": "Full width", - "userSettings": "Table settings" + "userSettings" : "Table settings", + "uploadFile" : "Upload policy JSON file", + "downloadFile" : "Download template as file" }, "publisher": { "selectedAssets": "Assets selected", @@ -113,10 +116,13 @@ "viewDetails" : "View details", "more" : "More actions", "editNotification" : "Edit quality topics", - "selectAtLeastOne" : "Actions require atleast one selection in the table", + "selectAtLeastOne" : "Requires at least one selection in the policies table", + "selectOnlyOne" : "Action requires only one selection from the table", "unauthorized" : "Functionality is disabled because of missing role. Ask your administrator to provide the required role for the functionality.", "noFunctionality" : "Functionality is not implemented yet", "onlyNotificationInStatusCreatedAllowed" : "Action only allowed with a selection of notifications in status \"Queued\".", + "createPolicy" : "Create policy", + "deletePolicy" : "Delete selected policies", "column": { "id": "ID", "idShort": "ID Short", @@ -158,7 +164,14 @@ "importNote": "Import Note", "importDate": "Import Date", "type": "Type", - "title": "Title" + "title" : "Title", + "policyId" : "Policy ID", + "validUntil" : "Valid until", + "createdOn" : "Valid from", + "policyName" : "Policy name", + "bpn" : "BPN selection", + "constraints" : "Constraints", + "accessType" : "Access type" } }, "dataLoading": { @@ -207,7 +220,9 @@ "minDate": "Please select a date that is after {{- minDate}}.", "currentDate": "Please select a date that is after {{- currentDate}}.", "generic": "Please enter valid data.", - "invalidBpn": "Must not be own BPN." + "invalidBpn" : "Must not be own BPN.", + "pastDate" : "Please select a date in the future.", + "minimumOneConstraint" : "Please add atleast one valid constraint to the policy" }, "unitTest": { "test01": "This is for unit tests purposes.", diff --git a/frontend/src/assets/locales/en/page.admin.json b/frontend/src/assets/locales/en/page.admin.json index ff2b82f53a..adb2c3693f 100644 --- a/frontend/src/assets/locales/en/page.admin.json +++ b/frontend/src/assets/locales/en/page.admin.json @@ -52,9 +52,51 @@ "selectAtleastOne": "Select atleast one contract to use the export functionality.", "exportAsCSV": ".CSV-File Export", "overview": "Overview", - "policyDetail": "Policy detail", + "policyDetail" : "Agreement details", "jsonViewer": "Json Viewer", "jsonTreeViewer": "Json Tree Viewer" + }, + "policyManagement" : { + "policyManagement" : "Policy management", + "deleteSuccess" : "Successfully deleted the selected policies.", + "deleteError" : "Error while deleting the selected policies.", + "confirm" : "Confirm", + "policyDeletion" : "Confirm deletion", + "deletionText" : "Do you want to delete the selected policies?", + "policy" : "Policy", + "create" : "creation", + "edit" : "edit", + "view" : "details", + "selectFileHint" : "Selected template filename", + "applyChange" : "Apply content of file overwriting existing data", + "chooseFile" : "Select a template file to be able to apply it", + "apply" : "Apply", + "policyName" : "Policy name", + "validUntil" : "Valid until", + "bpnls" : "BPNs", + "bpn" : "BPN", + "policyConstraints" : "Policy type", + "accessType" : "Access type", + "constraints" : "Constraints", + "logic" : "Logic", + "logicTypeHint" : "Please note that the order of the constraints is relevant", + "constraintLogicType" : "Constraint logic", + "leftOperand" : "Left operand", + "operator" : "Operator", + "rightOperand" : "Right operand", + "rightOperandHint" : "Please provide comma separated text for the operators IN, ISONEOF, ISALLOF, ISNONEOF", + "moveDownward" : "Move downward", + "moveUpward" : "Move upward", + "successMessage" : "Successfully saved policy", + "changeSuccessMessage" : "JSON file successfully applied", + "errorMessage" : "Error while trying to save policy", + "templateErrorMessage" : "The selected template file is not compliant. Please provide a valid .JSON template file.", + "invalidForm" : "Please make sure to submit a valid form", + "bpnsHint" : "Please provide a list of BPNs separated by comma", + "bpnHint" : "Please provide a BPN", + "savePolicy" : "Save policy", + "addConstraint" : "Add constraint" + } } } diff --git a/frontend/src/theme/base.scss b/frontend/src/theme/base.scss index 296abff8d0..4f29d58125 100644 --- a/frontend/src/theme/base.scss +++ b/frontend/src/theme/base.scss @@ -205,7 +205,7 @@ app-parts{ } } -app-notifications-tab { +app-notifications-tab, app-policies { .table-wrapper { background-color: white; @media screen and (max-height: 1049px) { @@ -268,7 +268,7 @@ app-multiselect { bottom: 0; } -app-parts, app-notifications-tab { +app-parts, app-notifications-tab, app-policies { .mat-mdc-table .mdc-data-table__row:last-child { border-bottom: 1px solid lightgrey; diff --git a/pom.xml b/pom.xml index bfb2fc5d3d..50f454925a 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ SPDX-License-Identifier: Apache-2.0 2023.0.1 24.1.0 3.0.2 - 1.6.12 + 1.6.14 0.2.6 0.9.4 1.1.0 @@ -133,7 +133,7 @@ SPDX-License-Identifier: Apache-2.0 commons-codec commons-codec - 1.16.1 + 1.17.0 diff --git a/tx-backend/openapi/traceability-foss-backend.json b/tx-backend/openapi/traceability-foss-backend.json index bc56ac8afa..87277b3e7b 100644 --- a/tx-backend/openapi/traceability-foss-backend.json +++ b/tx-backend/openapi/traceability-foss-backend.json @@ -22,36 +22,142 @@ } ], "paths" : { - "/notifications/{notificationId}/edit" : { - "put" : { + "/policies" : { + "get" : { "tags" : [ - "Notifications" + "Policies" ], - "summary" : "Update notification by id", - "description" : "The endpoint updates notification by their id.", - "operationId" : "updateNotification", - "parameters" : [ - { - "name" : "notificationId", - "in" : "path", - "required" : true, - "schema" : { - "type" : "integer", - "format" : "int64" + "summary" : "Get all policies ", + "description" : "The endpoint returns all policies .", + "operationId" : "policy", + "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "200" : { + "description" : "Returns the policies", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/IrsPolicyResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security" : [ + { + "oAuth2" : [ + "profile email" + ] } + ] + }, + "put" : { + "tags" : [ + "Policies" ], + "summary" : "Updates policies ", + "description" : "The endpoint updates policies.", + "operationId" : "updatePolicy", "requestBody" : { "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/EditNotificationRequest" + "$ref" : "#/components/schemas/UpdatePolicyRequest" } } }, "required" : true }, "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "401" : { "description" : "Authorization failed.", "content" : { @@ -62,6 +168,26 @@ } } }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "415" : { "description" : "Unsupported media type", "content" : { @@ -72,11 +198,51 @@ } } }, - "204" : { - "description" : "No content." + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Update successful", + "content" : { + "application/json" : {} + } + } + }, + "security" : [ + { + "oAuth2" : [ + "profile email" + ] + } + ] + }, + "post" : { + "tags" : [ + "Policies" + ], + "summary" : "Create a policy ", + "description" : "The endpoint creates a policy.", + "operationId" : "createPolicy", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/RegisterPolicyRequest" + } + } + }, + "required" : true + }, + "responses" : { + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -95,6 +261,16 @@ } } }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "429" : { "description" : "Too many requests.", "content" : { @@ -105,8 +281,18 @@ } } }, - "500" : { - "description" : "Internal server error.", + "200" : { + "description" : "Returns the policies", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/CreatePolicyResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -115,8 +301,18 @@ } } }, - "404" : { - "description" : "Not found.", + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -135,15 +331,59 @@ ] } }, - "/bpn-config" : { - "get" : { + "/notifications/{notificationId}/edit" : { + "put" : { "tags" : [ - "BpnEdcMapping" + "Notifications" ], - "summary" : "Get BPN EDC URL mappings", - "description" : "The endpoint returns a result of BPN EDC URL mappings.", - "operationId" : "getBpnEdcs", + "summary" : "Update notification by id", + "description" : "The endpoint updates notification by their id.", + "operationId" : "updateNotification", + "parameters" : [ + { + "name" : "notificationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } + ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/EditNotificationRequest" + } + } + }, + "required" : true + }, "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "204" : { + "description" : "No content." + }, "401" : { "description" : "Authorization failed.", "content" : { @@ -154,8 +394,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -174,6 +414,55 @@ } } }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security" : [ + { + "oAuth2" : [ + "profile email" + ] + } + ] + } + }, + "/bpn-config" : { + "get" : { + "tags" : [ + "BpnEdcMapping" + ], + "summary" : "Get BPN EDC URL mappings", + "description" : "The endpoint returns a result of BPN EDC URL mappings.", + "operationId" : "getBpnEdcs", + "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "400" : { "description" : "Bad request.", "content" : { @@ -184,8 +473,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -194,8 +483,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -219,8 +508,28 @@ } } }, - "404" : { - "description" : "Not found.", + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -261,8 +570,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -271,8 +580,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -281,8 +590,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -291,8 +600,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -301,8 +610,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -311,17 +620,12 @@ } } }, - "200" : { - "description" : "Returns the paged result found for BpnEdcMapping", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : 0, - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/BpnEdcMappingResponse" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } @@ -336,12 +640,17 @@ } } }, - "404" : { - "description" : "Not found.", + "200" : { + "description" : "Returns the paged result found for BpnEdcMapping", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "minItems" : 0, + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/BpnEdcMappingResponse" + } } } } @@ -378,8 +687,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -388,8 +697,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -398,8 +707,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -408,8 +717,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -418,8 +727,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -428,17 +737,12 @@ } } }, - "200" : { - "description" : "Returns the paged result found for BpnEdcMapping", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : 0, - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/BpnEdcMappingResponse" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } @@ -453,12 +757,17 @@ } } }, - "404" : { - "description" : "Not found.", + "200" : { + "description" : "Returns the paged result found for BpnEdcMapping", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "minItems" : 0, + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/BpnEdcMappingResponse" + } } } } @@ -492,18 +801,18 @@ } ], "responses" : { - "200" : { - "description" : "Returns submodel payload", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { - "type" : "string" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "401" : { - "description" : "Authorization failed.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -512,8 +821,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -522,8 +831,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -532,18 +841,18 @@ } } }, - "400" : { - "description" : "Bad request.", + "200" : { + "description" : "Returns submodel payload", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "type" : "string" } } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -552,8 +861,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -562,8 +871,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -606,14 +915,11 @@ } } }, - "required" : true - }, - "responses" : { - "200" : { - "description" : "Ok." - }, - "401" : { - "description" : "Authorization failed.", + "required" : true + }, + "responses" : { + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -622,8 +928,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -632,8 +938,11 @@ } } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -642,8 +951,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -652,8 +961,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -662,11 +971,8 @@ } } }, - "204" : { - "description" : "No Content." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -675,8 +981,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -684,6 +990,9 @@ } } } + }, + "204" : { + "description" : "No Content." } }, "security" : [ @@ -714,8 +1023,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -724,8 +1033,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -734,8 +1043,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -744,8 +1053,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -754,8 +1063,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -764,12 +1073,12 @@ } } }, - "201" : { - "description" : "Created.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/NotificationIdResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } @@ -784,12 +1093,12 @@ } } }, - "404" : { - "description" : "Not found.", + "201" : { + "description" : "Created.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/NotificationIdResponse" } } } @@ -834,8 +1143,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -844,8 +1153,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -857,8 +1166,8 @@ "204" : { "description" : "No content." }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -867,8 +1176,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -877,8 +1186,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -887,8 +1196,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -897,8 +1206,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -947,11 +1256,8 @@ "required" : true }, "responses" : { - "200" : { - "description" : "Ok." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -960,8 +1266,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -970,11 +1276,14 @@ } } }, + "200" : { + "description" : "Ok." + }, "204" : { "description" : "No content." }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -983,8 +1292,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -993,8 +1302,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1003,8 +1312,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1013,8 +1322,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1053,11 +1362,8 @@ } ], "responses" : { - "200" : { - "description" : "Ok." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1066,8 +1372,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1076,8 +1382,11 @@ } } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1086,8 +1395,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1096,8 +1405,11 @@ } } }, - "429" : { - "description" : "Too many requests.", + "204" : { + "description" : "No content." + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1106,11 +1418,8 @@ } } }, - "204" : { - "description" : "No content." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1119,8 +1428,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1159,11 +1468,8 @@ } ], "responses" : { - "200" : { - "description" : "Ok." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1172,8 +1478,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1182,8 +1488,11 @@ } } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1192,8 +1501,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1202,8 +1511,11 @@ } } }, - "429" : { - "description" : "Too many requests.", + "204" : { + "description" : "No content." + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1212,11 +1524,8 @@ } } }, - "204" : { - "description" : "No content." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1225,8 +1534,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1257,55 +1566,15 @@ "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/PageableFilterRequest" - } - } - }, - "required" : true - }, - "responses" : { - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } + "$ref" : "#/components/schemas/PageableFilterRequest" } } }, - "429" : { - "description" : "Too many requests.", + "required" : true + }, + "responses" : { + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1458,8 +1727,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1468,8 +1737,48 @@ } } }, - "404" : { - "description" : "Not found.", + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1507,8 +1816,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1517,8 +1826,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1527,8 +1836,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1537,8 +1846,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1547,8 +1856,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1557,12 +1866,12 @@ } } }, - "201" : { - "description" : "Created.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/CreateNotificationContractResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } @@ -1577,12 +1886,12 @@ } } }, - "404" : { - "description" : "Not found.", + "201" : { + "description" : "Created.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/CreateNotificationContractResponse" } } } @@ -1616,108 +1925,108 @@ "required" : true }, "responses" : { - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Bad request." + "message" : "Too many requests." } } } } }, - "401" : { - "description" : "Authorization failed.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Authorization failed." + "message" : "Bad request." } } } } }, - "200" : { - "description" : "Ok.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : 0, - "type" : "array", - "description" : "PageResults", - "items" : { - "$ref" : "#/components/schemas/PageResultContractResponse" + "type" : "string", + "example" : { + "message" : "Not found." } } } } }, - "403" : { - "description" : "Forbidden.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Forbidden." + "message" : "Internal server error." } } } } }, - "429" : { - "description" : "Too many requests.", + "200" : { + "description" : "Ok.", "content" : { "application/json" : { "schema" : { - "type" : "string", - "example" : { - "message" : "Too many requests." + "maxItems" : 2147483647, + "minItems" : 0, + "type" : "array", + "description" : "PageResults", + "items" : { + "$ref" : "#/components/schemas/PageResultContractResponse" } } } } }, - "415" : { - "description" : "Unsupported media type.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Unsupported media type." + "message" : "Authorization failed." } } } } }, - "500" : { - "description" : "Internal server error.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Internal server error." + "message" : "Forbidden." } } } } }, - "404" : { - "description" : "Not found.", + "415" : { + "description" : "Unsupported media type.", "content" : { "application/json" : { "schema" : { "type" : "string", "example" : { - "message" : "Not found." + "message" : "Unsupported media type." } } } @@ -1764,11 +2073,8 @@ "required" : true }, "responses" : { - "204" : { - "description" : "No Content." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1777,8 +2083,11 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "204" : { + "description" : "No Content." + }, + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1787,8 +2096,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1797,8 +2106,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -1807,8 +2116,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -1817,8 +2126,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1827,8 +2136,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1880,11 +2189,8 @@ } }, "responses" : { - "204" : { - "description" : "No Content." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -1893,8 +2199,11 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "204" : { + "description" : "No Content." + }, + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -1903,18 +2212,18 @@ } } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "OK.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/ImportResponse" } } } }, - "400" : { - "description" : "Bad request.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -1933,18 +2242,18 @@ } } }, - "200" : { - "description" : "OK.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ImportResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -1953,8 +2262,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -1992,8 +2301,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -2002,8 +2311,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -2012,8 +2321,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -2022,8 +2331,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -2032,8 +2341,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -2042,11 +2351,8 @@ } } }, - "201" : { - "description" : "Created." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -2055,8 +2361,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -2064,6 +2370,9 @@ } } } + }, + "201" : { + "description" : "Created." } }, "security" : [ @@ -2094,6 +2403,26 @@ "required" : true }, "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "401" : { "description" : "Authorization failed.", "content" : { @@ -2104,8 +2433,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -2124,8 +2453,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -2134,8 +2463,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -2335,26 +2664,6 @@ } } } - }, - "500" : { - "description" : "Internal server error.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } } }, "security" : [ @@ -2385,8 +2694,8 @@ "required" : true }, "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -2395,8 +2704,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -2405,8 +2714,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -2415,8 +2724,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -2425,8 +2734,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -2435,11 +2744,8 @@ } } }, - "201" : { - "description" : "Created." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -2448,8 +2754,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -2457,6 +2763,9 @@ } } } + }, + "201" : { + "description" : "Created." } }, "security" : [ @@ -2679,8 +2988,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -2689,8 +2998,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -2699,8 +3008,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -2709,8 +3018,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -2719,8 +3028,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -2729,8 +3038,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -2739,8 +3048,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -2778,6 +3087,76 @@ } ], "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns the assets found", "content" : { @@ -2959,78 +3338,8 @@ "type" : "string", "example" : "TODO" } - } - } - } - } - } - }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "500" : { - "description" : "Internal server error.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + } + } } } } @@ -3072,6 +3381,26 @@ "required" : true }, "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "401" : { "description" : "Authorization failed.", "content" : { @@ -3082,8 +3411,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -3102,6 +3431,26 @@ } } }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns the updated asset", "content" : { @@ -3288,46 +3637,6 @@ } } } - }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "500" : { - "description" : "Internal server error.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } } }, "security" : [ @@ -3358,28 +3667,8 @@ } ], "responses" : { - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "403" : { - "description" : "Forbidden.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -3398,16 +3687,6 @@ } } }, - "429" : { - "description" : "Too many requests.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, "200" : { "description" : "Returns the assets found", "content" : { @@ -3595,6 +3874,46 @@ } } }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "500" : { "description" : "Internal server error.", "content" : { @@ -3604,7 +3923,44 @@ } } } + } + }, + "security" : [ + { + "oAuth2" : [ + "profile email" + ] + } + ] + }, + "patch" : { + "tags" : [ + "AssetsAsBuilt" + ], + "summary" : "Updates asset", + "description" : "The endpoint updates asset by provided quality type.", + "operationId" : "updateAsset_1", + "parameters" : [ + { + "name" : "assetId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } + ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UpdateAssetRequest" + } + } }, + "required" : true + }, + "responses" : { "404" : { "description" : "Not found.", "content" : { @@ -3614,44 +3970,67 @@ } } } - } - }, - "security" : [ - { - "oAuth2" : [ - "profile email" - ] - } - ] - }, - "patch" : { - "tags" : [ - "AssetsAsBuilt" - ], - "summary" : "Updates asset", - "description" : "The endpoint updates asset by provided quality type.", - "operationId" : "updateAsset_1", - "parameters" : [ - { - "name" : "assetId", - "in" : "path", - "required" : true, - "schema" : { - "type" : "string" - } - } - ], - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/UpdateAssetRequest" - } - } }, - "required" : true - }, - "responses" : { + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns the updated asset", "content" : { @@ -3838,9 +4217,28 @@ } } } - }, - "401" : { - "description" : "Authorization failed.", + } + }, + "security" : [ + { + "oAuth2" : [ + "profile email" + ] + } + ] + } + }, + "/registry/reload" : { + "get" : { + "tags" : [ + "Registry" + ], + "summary" : "Triggers reload of shell descriptors", + "description" : "The endpoint Triggers reload of shell descriptors.", + "operationId" : "reload", + "responses" : { + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -3849,8 +4247,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -3859,8 +4257,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -3869,8 +4267,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -3879,8 +4277,11 @@ } } }, - "429" : { - "description" : "Too many requests.", + "202" : { + "description" : "Created registry reload job." + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -3889,8 +4290,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -3899,8 +4300,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -3919,17 +4320,27 @@ ] } }, - "/registry/reload" : { + "/policies/{policyId}" : { "get" : { "tags" : [ - "Registry" + "Policies" + ], + "summary" : "Gets policy by id", + "description" : "The endpoint returns policy by id.", + "operationId" : "getPolicyById", + "parameters" : [ + { + "name" : "policyId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], - "summary" : "Triggers reload of shell descriptors", - "description" : "The endpoint Triggers reload of shell descriptors.", - "operationId" : "reload", "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -3938,8 +4349,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -3948,21 +4359,18 @@ } } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Returns the policies", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/PolicyResponse" } } } }, - "202" : { - "description" : "Created registry reload job." - }, - "400" : { - "description" : "Bad request.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -3981,8 +4389,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -3991,8 +4399,18 @@ } } }, - "404" : { - "description" : "Not found.", + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -4009,19 +4427,27 @@ ] } ] - } - }, - "/policies" : { - "get" : { + }, + "delete" : { "tags" : [ "Policies" ], - "summary" : "Get all policies ", - "description" : "The endpoint returns all policies .", - "operationId" : "policy", + "summary" : "Deletes a policy ", + "description" : "The endpoint deletes policies.", + "operationId" : "deletePolicy", + "parameters" : [ + { + "name" : "policyId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } + ], "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -4030,18 +4456,14 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "200" : { + "description" : "Deletion successful", "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } + "application/json" : {} } }, - "403" : { - "description" : "Forbidden.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -4050,8 +4472,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -4070,18 +4492,18 @@ } } }, - "200" : { - "description" : "Returns the policies", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/PolicyResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4090,8 +4512,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -4130,23 +4552,18 @@ } ], "responses" : { - "200" : { - "description" : "OK.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "type" : "array", - "description" : "Notifications", - "items" : { - "$ref" : "#/components/schemas/NotificationResponse" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "401" : { - "description" : "Authorization failed.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -4155,18 +4572,23 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "200" : { + "description" : "OK.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "type" : "array", + "description" : "Notifications", + "items" : { + "$ref" : "#/components/schemas/NotificationResponse" + } } } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -4175,8 +4597,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -4185,8 +4607,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -4195,8 +4617,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4205,8 +4627,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -4270,9 +4692,29 @@ "RECEIVER" ] } - } - ], - "responses" : { + } + ], + "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns a distinct filter values for given fieldName.", "content" : { @@ -4298,8 +4740,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -4318,18 +4760,8 @@ } } }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4347,16 +4779,6 @@ } } } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } } }, "security" : [ @@ -4377,8 +4799,8 @@ "description" : "The endpoint can return limited data based on the user role", "operationId" : "dashboard", "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -4387,18 +4809,18 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "200" : { + "description" : "Returns dashboard data", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/DashboardResponse" } } } }, - "403" : { - "description" : "Forbidden.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -4407,8 +4829,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -4427,18 +4849,18 @@ } } }, - "200" : { - "description" : "Returns dashboard data", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/DashboardResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4447,8 +4869,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -4487,11 +4909,8 @@ } ], "responses" : { - "204" : { - "description" : "No Content." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -4500,8 +4919,11 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "204" : { + "description" : "No Content." + }, + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -4510,18 +4932,18 @@ } } }, - "200" : { - "description" : "OK.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ImportReportResponse" + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "403" : { - "description" : "Forbidden.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -4530,8 +4952,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -4540,8 +4962,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4560,12 +4982,12 @@ } } }, - "404" : { - "description" : "Not found.", + "200" : { + "description" : "OK.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "$ref" : "#/components/schemas/ImportReportResponse" } } } @@ -4607,6 +5029,46 @@ } ], "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns the paged result found for Asset", "content" : { @@ -4799,26 +5261,6 @@ } } }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, "403" : { "description" : "Forbidden.", "content" : { @@ -4829,18 +5271,8 @@ } } }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -4858,16 +5290,6 @@ } } } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } } }, "security" : [ @@ -4941,23 +5363,18 @@ } ], "responses" : { - "200" : { - "description" : "Returns a distinct filter values for given fieldName.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : 0, - "type" : "array", - "items" : { - "type" : "string" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "401" : { - "description" : "Authorization failed.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -4966,18 +5383,23 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "200" : { + "description" : "Returns a distinct filter values for given fieldName.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "minItems" : 0, + "type" : "array", + "items" : { + "type" : "string" + } } } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -4986,8 +5408,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -4996,8 +5418,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -5006,8 +5428,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -5016,8 +5438,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -5052,9 +5474,29 @@ "schema" : { "type" : "string" } - } - ], - "responses" : { + } + ], + "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns the asset by childId", "content" : { @@ -5252,8 +5694,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -5272,18 +5714,8 @@ } } }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -5301,16 +5733,6 @@ } } } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } } }, "security" : [ @@ -5349,6 +5771,76 @@ } ], "responses" : { + "404" : { + "description" : "Not found.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "400" : { + "description" : "Bad request.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "401" : { + "description" : "Authorization failed.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "429" : { + "description" : "Too many requests.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "403" : { + "description" : "Forbidden.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, + "500" : { + "description" : "Internal server error.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorResponse" + } + } + } + }, "200" : { "description" : "Returns the paged result found for Asset", "content" : { @@ -5540,76 +6032,6 @@ } } } - }, - "401" : { - "description" : "Authorization failed.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "415" : { - "description" : "Unsupported media type", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "403" : { - "description" : "Forbidden.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "400" : { - "description" : "Bad request.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "429" : { - "description" : "Too many requests.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "500" : { - "description" : "Internal server error.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } - }, - "404" : { - "description" : "Not found.", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" - } - } - } } }, "security" : [ @@ -5683,23 +6105,18 @@ } ], "responses" : { - "200" : { - "description" : "Returns a distinct filter values for given fieldName.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { - "maxItems" : 2147483647, - "minItems" : 0, - "type" : "array", - "items" : { - "type" : "string" - } + "$ref" : "#/components/schemas/ErrorResponse" } } } }, - "401" : { - "description" : "Authorization failed.", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -5708,18 +6125,23 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "200" : { + "description" : "Returns a distinct filter values for given fieldName.", "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ErrorResponse" + "maxItems" : 2147483647, + "minItems" : 0, + "type" : "array", + "items" : { + "type" : "string" + } } } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -5728,8 +6150,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -5738,8 +6160,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -5748,8 +6170,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -5758,8 +6180,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -5802,8 +6224,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -5812,8 +6234,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -5822,8 +6244,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -5832,8 +6254,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -5842,8 +6264,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -5852,8 +6274,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -5862,8 +6284,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -6088,8 +6510,8 @@ } } }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -6098,8 +6520,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -6108,8 +6530,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -6118,8 +6540,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -6128,8 +6550,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -6138,8 +6560,8 @@ } } }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -6148,8 +6570,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -6177,11 +6599,8 @@ "description" : "Deletes all submodels from the system.", "operationId" : "deleteSubmodels", "responses" : { - "200" : { - "description" : "Ok." - }, - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -6190,8 +6609,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -6200,8 +6619,11 @@ } } }, - "403" : { - "description" : "Forbidden.", + "200" : { + "description" : "Ok." + }, + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -6210,8 +6632,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -6220,8 +6642,8 @@ } } }, - "429" : { - "description" : "Too many requests.", + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -6230,11 +6652,8 @@ } } }, - "204" : { - "description" : "No Content." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -6243,8 +6662,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -6252,6 +6671,9 @@ } } } + }, + "204" : { + "description" : "No Content." } }, "security" : [ @@ -6282,8 +6704,8 @@ } ], "responses" : { - "401" : { - "description" : "Authorization failed.", + "404" : { + "description" : "Not found.", "content" : { "application/json" : { "schema" : { @@ -6292,8 +6714,8 @@ } } }, - "415" : { - "description" : "Unsupported media type", + "400" : { + "description" : "Bad request.", "content" : { "application/json" : { "schema" : { @@ -6302,8 +6724,8 @@ } } }, - "403" : { - "description" : "Forbidden.", + "401" : { + "description" : "Authorization failed.", "content" : { "application/json" : { "schema" : { @@ -6312,8 +6734,8 @@ } } }, - "400" : { - "description" : "Bad request.", + "429" : { + "description" : "Too many requests.", "content" : { "application/json" : { "schema" : { @@ -6322,8 +6744,11 @@ } } }, - "429" : { - "description" : "Too many requests.", + "204" : { + "description" : "Deleted." + }, + "403" : { + "description" : "Forbidden.", "content" : { "application/json" : { "schema" : { @@ -6332,14 +6757,8 @@ } } }, - "200" : { - "description" : "Okay" - }, - "204" : { - "description" : "Deleted." - }, - "500" : { - "description" : "Internal server error.", + "415" : { + "description" : "Unsupported media type", "content" : { "application/json" : { "schema" : { @@ -6348,8 +6767,8 @@ } } }, - "404" : { - "description" : "Not found.", + "500" : { + "description" : "Internal server error.", "content" : { "application/json" : { "schema" : { @@ -6357,6 +6776,9 @@ } } } + }, + "200" : { + "description" : "Okay" } }, "security" : [ @@ -6371,6 +6793,39 @@ }, "components" : { "schemas" : { + "UpdatePolicyRequest" : { + "type" : "object", + "properties" : { + "businessPartnerNumbers" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "policyIds" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "validUntil" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "ErrorResponse" : { + "type" : "object", + "properties" : { + "message" : { + "maxLength" : 1000, + "minLength" : 0, + "pattern" : "^.*$", + "type" : "string", + "example" : "Access Denied" + } + } + }, "EditNotificationRequest" : { "required" : [ "affectedPartIds", @@ -6428,18 +6883,6 @@ } } }, - "ErrorResponse" : { - "type" : "object", - "properties" : { - "message" : { - "maxLength" : 1000, - "minLength" : 0, - "pattern" : "^.*$", - "type" : "string", - "example" : "Access Denied" - } - } - }, "BpnMappingRequest" : { "required" : [ "bpn", @@ -6473,6 +6916,142 @@ } } }, + "Constraint" : { + "type" : "object", + "properties" : { + "leftOperand" : { + "type" : "string", + "example" : "string" + }, + "operator" : { + "$ref" : "#/components/schemas/Operator" + }, + "odrl:rightOperand" : { + "type" : "string", + "example" : "string" + } + } + }, + "Constraints" : { + "type" : "object", + "properties" : { + "and" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Constraint" + } + }, + "or" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Constraint" + } + } + } + }, + "Context" : { + "type" : "object", + "properties" : { + "odrl" : { + "type" : "string" + } + } + }, + "Operator" : { + "type" : "object", + "properties" : { + "@id" : { + "type" : "string", + "example" : "odrl:eq", + "enum" : [ + "eq", + "neq", + "lt", + "gt", + "in", + "lteq", + "gteq", + "isA", + "hasPart", + "isPartOf", + "isOneOf", + "isAllOf", + "isNoneOf" + ] + } + } + }, + "Payload" : { + "type" : "object", + "properties" : { + "@context" : { + "$ref" : "#/components/schemas/Context" + }, + "@id" : { + "type" : "string" + }, + "policy" : { + "$ref" : "#/components/schemas/Policy" + } + } + }, + "Permission" : { + "type" : "object", + "properties" : { + "action" : { + "type" : "string", + "example" : "use", + "enum" : [ + "access", + "use" + ] + }, + "constraint" : { + "$ref" : "#/components/schemas/Constraints" + } + } + }, + "Policy" : { + "type" : "object", + "properties" : { + "policyId" : { + "type" : "string", + "example" : "f253718e-a270-4367-901b-9d50d9bd8462" + }, + "createdOn" : { + "type" : "string", + "format" : "date-time" + }, + "validUntil" : { + "type" : "string", + "format" : "date-time" + }, + "permissions" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Permission" + } + } + } + }, + "RegisterPolicyRequest" : { + "type" : "object", + "properties" : { + "validUntil" : { + "type" : "string", + "format" : "date-time" + }, + "businessPartnerNumber" : { + "type" : "string" + }, + "payload" : { + "$ref" : "#/components/schemas/Payload" + } + } + }, + "CreatePolicyResponse" : { + "type" : "object" + }, "StartNotificationRequest" : { "required" : [ "receiverBpn", @@ -7248,6 +7827,57 @@ } } }, + "IrsPolicyResponse" : { + "type" : "object", + "properties" : { + "validUntil" : { + "type" : "string", + "format" : "date-time" + }, + "payload" : { + "$ref" : "#/components/schemas/Payload" + } + }, + "example" : [ + { + "validUntil" : "2025-12-12T23:59:59.999Z", + "payload" : { + "@context" : { + "@vocab" : "https://w3id.org/edc/v0.0.1/ns/", + "edc" : "https://w3id.org/edc/v0.0.1/ns/", + "cx-policy" : "https://w3id.org/catenax/policy/", + "odrl" : "http://www.w3.org/ns/odrl/2/" + }, + "@id" : "policy-id", + "policy" : { + "odrl:permission" : [ + { + "odrl:action" : "use", + "odrl:constraint" : { + "odrl:and" : [ + { + "odrl:leftOperand" : "Membership", + "odrl:operator" : { + "@id" : "odrl:eq" + }, + "odrl:rightOperand" : "active" + }, + { + "odrl:leftOperand" : "PURPOSE", + "odrl:operator" : { + "@id" : "odrl:eq" + }, + "odrl:rightOperand" : "ID 3.1 Trace" + } + ] + } + } + ] + } + } + } + ] + }, "ConstraintResponse" : { "type" : "object", "properties" : { @@ -7332,6 +7962,9 @@ "items" : { "$ref" : "#/components/schemas/PermissionResponse" } + }, + "businessPartnerNumber" : { + "type" : "string" } } }, diff --git a/tx-backend/pom.xml b/tx-backend/pom.xml index cb92614787..5a8baf2433 100644 --- a/tx-backend/pom.xml +++ b/tx-backend/pom.xml @@ -322,6 +322,24 @@ SPDX-License-Identifier: Apache-2.0 + + copy-testdata + process-test-resources + + copy-resources + + + target/classes/testdata/jsonfiles + + + testdata + + CX_Testdata_MessagingTest_v* + + + + + diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/rest/PolicyController.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/rest/PolicyController.java deleted file mode 100644 index d6247aaf0e..0000000000 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/rest/PolicyController.java +++ /dev/null @@ -1,107 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -package org.eclipse.tractusx.traceability.assets.application.importpoc.rest; - - -import assets.importpoc.ErrorResponse; -import assets.importpoc.PolicyResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.traceability.assets.application.importpoc.PolicyService; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -@Slf4j -@RequiredArgsConstructor -@RestController -@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_SUPERVISOR')") -@Tag(name = "Policies") -@RequestMapping(path = "/policies") -public class PolicyController { - - private final PolicyService policyService; - - @Operation(operationId = "policy", - summary = "Get all policies ", - tags = {"Policies"}, - description = "The endpoint returns all policies .", - security = @SecurityRequirement(name = "oAuth2", scopes = "profile email")) - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Returns the policies", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = PolicyResponse.class))}), - @ApiResponse( - responseCode = "400", - description = "Bad request.", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ErrorResponse.class))), - @ApiResponse( - responseCode = "401", - description = "Authorization failed.", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ErrorResponse.class))), - - @ApiResponse( - responseCode = "403", - description = "Forbidden.", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ErrorResponse.class))), - @ApiResponse( - responseCode = "404", - description = "Not found.", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ErrorResponse.class))), - @ApiResponse( - responseCode = "415", - description = "Unsupported media type", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ErrorResponse.class))), - @ApiResponse( - responseCode = "429", - description = "Too many requests.", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ErrorResponse.class))), - @ApiResponse( - responseCode = "500", - description = "Internal server error.", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ErrorResponse.class)))}) - @GetMapping() - public List policy() { - return policyService.getAllPolicies(); - } -} diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/PolicyService.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/exception/PolicyBadRequestException.java similarity index 66% rename from tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/PolicyService.java rename to tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/exception/PolicyBadRequestException.java index fa165125df..6531d66b94 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/PolicyService.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/exception/PolicyBadRequestException.java @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -16,17 +16,11 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.traceability.assets.application.importpoc; -import assets.importpoc.PolicyResponse; +package org.eclipse.tractusx.traceability.assets.domain.importpoc.exception; -import java.util.List; -import java.util.Optional; - -public interface PolicyService { - List getAllPolicies(); - - PolicyResponse getPolicyById(String id); - - Optional getFirstPolicyMatchingApplicationConstraint(); +public class PolicyBadRequestException extends RuntimeException{ + public PolicyBadRequestException(String message) { + super(message); + } } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/EdcAssetCreationService.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/EdcAssetCreationService.java index 8cf7aa560b..d85fb1f48b 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/EdcAssetCreationService.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/EdcAssetCreationService.java @@ -19,7 +19,7 @@ package org.eclipse.tractusx.traceability.assets.domain.importpoc.service; -import assets.importpoc.PolicyResponse; +import policies.response.PolicyResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.irs.edc.client.asset.EdcAssetService; @@ -30,14 +30,14 @@ import org.eclipse.tractusx.irs.edc.client.policy.model.exception.CreateEdcPolicyDefinitionException; import org.eclipse.tractusx.irs.edc.client.policy.model.exception.EdcPolicyDefinitionAlreadyExists; import org.eclipse.tractusx.irs.edc.client.policy.service.EdcPolicyDefinitionService; -import org.eclipse.tractusx.traceability.assets.application.importpoc.PolicyService; +import org.eclipse.tractusx.traceability.policies.application.service.PolicyService; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.UUID; -import static org.eclipse.tractusx.traceability.assets.application.importpoc.mapper.PolicyMapper.mapToEdcPolicyRequest; +import static org.eclipse.tractusx.traceability.policies.application.mapper.PolicyMapper.mapToEdcPolicyRequest; @Slf4j @Service @@ -53,7 +53,7 @@ public class EdcAssetCreationService { String registryUrlWithPath = null; public String createEdcContractDefinitionsForDtrAndSubmodel(String policyId) throws CreateEdcPolicyDefinitionException, CreateEdcAssetException, CreateEdcContractDefinitionException { - PolicyResponse policy = policyService.getPolicyById(policyId); + PolicyResponse policy = policyService.getPolicy(policyId); String createdPolicyId; try { boolean exists = edcPolicyDefinitionService.policyDefinitionExists(policyId); diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobClient.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobClient.java new file mode 100644 index 0000000000..66006dbcbd --- /dev/null +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobClient.java @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.traceability.assets.infrastructure.base.irs; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.RegisterJobRequest; +import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IRSResponse; +import org.jetbrains.annotations.Nullable; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import static org.eclipse.tractusx.traceability.common.config.RestTemplateConfiguration.IRS_REGULAR_TEMPLATE; + +@Slf4j +@Component +public class JobClient { + + private final RestTemplate irsRegularTemplate; + + public JobClient(@Qualifier(IRS_REGULAR_TEMPLATE) RestTemplate irsRegularTemplate) { + this.irsRegularTemplate = irsRegularTemplate; + } + + public void registerJob(RegisterJobRequest registerJobRequest) { + irsRegularTemplate.exchange("/irs/jobs", HttpMethod.POST, new HttpEntity<>(registerJobRequest), Void.class); + } + + @Nullable + public IRSResponse getIrsJobDetailResponse(String jobId) { + return irsRegularTemplate.exchange("/irs/jobs/" + jobId, HttpMethod.GET, null, new ParameterizedTypeReference() { + }).getBody(); + } +} diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImpl.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImpl.java index 9c1edefafc..9d9dab73d8 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImpl.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImpl.java @@ -33,8 +33,6 @@ import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IRSResponse; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.JobStatus; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.factory.IrsResponseAssetMapper; -import org.eclipse.tractusx.traceability.bpn.domain.service.BpnService; -import org.eclipse.tractusx.traceability.bpn.infrastructure.repository.BpnRepository; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @@ -50,41 +48,34 @@ @Service public class JobRepositoryImpl implements JobRepository { - private final BpnRepository bpnRepository; - - private final BpnService bpnService; private final TraceabilityProperties traceabilityProperties; private final AssetCallbackRepository assetAsBuiltCallbackRepository; private final AssetCallbackRepository assetAsPlannedCallbackRepository; private static final String JOB_STATUS_COMPLETED = "COMPLETED"; - private static final String JOB_STATUS_RUNNING = "RUNNING"; private final IrsResponseAssetMapper assetMapperFactory; - private final IrsClient irsClient; + private final JobClient jobClient; public JobRepositoryImpl( - IrsClient irsClient, - BpnRepository bpnRepository, - BpnService bpnService, TraceabilityProperties traceabilityProperties, + JobClient jobClient, + TraceabilityProperties traceabilityProperties, @Qualifier("assetAsBuiltRepositoryImpl") AssetCallbackRepository assetAsBuiltCallbackRepository, @Qualifier("assetAsPlannedRepositoryImpl") AssetCallbackRepository assetAsPlannedCallbackRepository, IrsResponseAssetMapper assetMapperFactory) { - this.bpnRepository = bpnRepository; - this.bpnService = bpnService; this.traceabilityProperties = traceabilityProperties; this.assetAsBuiltCallbackRepository = assetAsBuiltCallbackRepository; this.assetAsPlannedCallbackRepository = assetAsPlannedCallbackRepository; - this.irsClient = irsClient; + this.jobClient = jobClient; this.assetMapperFactory = assetMapperFactory; } @Override public void createJobToResolveAssets(String globalAssetId, Direction direction, List aspects, BomLifecycle bomLifecycle) { RegisterJobRequest registerJobRequest = RegisterJobRequest.buildJobRequest(globalAssetId, traceabilityProperties.getBpn().toString(), direction, aspects, bomLifecycle, traceabilityProperties.getUrl()); - this.irsClient.registerJob(registerJobRequest); + this.jobClient.registerJob(registerJobRequest); } @@ -94,7 +85,7 @@ public void handleJobFinishedCallback(String jobId, String state) { return; } - final IRSResponse jobResponseIRS = this.irsClient.getIrsJobDetailResponse(jobId); + final IRSResponse jobResponseIRS = this.jobClient.getIrsJobDetailResponse(jobId); if (jobResponseIRS == null) { return; @@ -136,13 +127,8 @@ void saveOrUpdateAssets(AssetCallbackRepository repository, AssetBase asset) { } - public static boolean jobCompleted(JobStatus jobStatus) { return JOB_STATUS_COMPLETED.equals(jobStatus.state()); } - public static boolean jobRunning(JobStatus jobStatus) { - return JOB_STATUS_RUNNING.equals(jobStatus.state()); - } - } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/PolicyRepositoryImpl.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/PolicyRepositoryImpl.java deleted file mode 100644 index b28d014020..0000000000 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/PolicyRepositoryImpl.java +++ /dev/null @@ -1,129 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ -package org.eclipse.tractusx.traceability.assets.infrastructure.base.irs; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.irs.edc.client.policy.Constraints; -import org.eclipse.tractusx.traceability.assets.domain.base.PolicyRepository; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; -import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.stream.Stream; - -import static org.apache.commons.collections4.ListUtils.emptyIfNull; - -@Slf4j -@Service -@RequiredArgsConstructor -public class PolicyRepositoryImpl implements PolicyRepository { - - private final IrsClient irsClient; - private final TraceabilityProperties traceabilityProperties; - - @Override - public List getPolicies() { - return irsClient.getPolicies(); - } - - @Override - public void createIrsPolicyIfMissing() { - log.info("Check if irs policy exists"); - final List irsPolicies = this.irsClient.getPolicies(); - final List irsPoliciesIds = irsPolicies.stream().map(policyResponse -> policyResponse.payload().policyId()).toList(); - log.info("Irs has following policies: {}", irsPoliciesIds); - - log.info("Required constraints - 2 -"); - log.info("First constraint requirements: leftOperand {} operator {} and rightOperand {}", traceabilityProperties.getLeftOperand(), traceabilityProperties.getOperatorType(), traceabilityProperties.getRightOperand()); - log.info("Second constraint requirements: eftOperand {} operator {} and rightOperand {}", traceabilityProperties.getLeftOperandSecond(), traceabilityProperties.getOperatorTypeSecond(), traceabilityProperties.getRightOperandSecond()); - - IrsPolicyResponse matchingPolicy = findMatchingPolicy(irsPolicies); - - if (matchingPolicy == null) { - createMissingPolicies(); - } else { - checkAndUpdatePolicy(matchingPolicy); - } - } - - private IrsPolicyResponse findMatchingPolicy(List irsPolicies) { - return irsPolicies.stream() - .filter(irsPolicy -> { - irsPolicy.payload().policy().getPermissions().forEach(permission -> { - Constraints constraint = permission.getConstraint(); - if (constraint != null) { - constraint.getAnd().forEach(constraint1 -> log.info("From IRS Policy Response -> Leftoperand {} operator {} and rightOperand {}", constraint1.getLeftOperand(), constraint1.getOperator(), constraint1.getRightOperand())); - } - }); - - // Check if all constraints exist in the policy - boolean firstConstraintExists = emptyIfNull(irsPolicy.payload().policy().getPermissions()).stream() - .flatMap(permission -> { - Constraints constraint = permission.getConstraint(); - return constraint != null ? emptyIfNull(constraint.getAnd()).stream() : Stream.empty(); - }) - .anyMatch(constraint -> constraint.getRightOperand().equals(traceabilityProperties.getRightOperand())) - || emptyIfNull(irsPolicy.payload().policy().getPermissions()).stream() - .flatMap(permission -> { - Constraints constraint = permission.getConstraint(); - return constraint != null ? emptyIfNull(constraint.getOr()).stream() : Stream.empty(); - }) - .anyMatch(constraint -> constraint.getRightOperand().equals(traceabilityProperties.getRightOperand())); - - boolean secondConstraintExists = emptyIfNull(irsPolicy.payload().policy().getPermissions()).stream() - .flatMap(permission -> { - Constraints constraint = permission.getConstraint(); - return constraint != null ? emptyIfNull(constraint.getAnd()).stream() : Stream.empty(); - }) - .anyMatch(constraint -> constraint.getRightOperand().equals(traceabilityProperties.getRightOperandSecond())) - || emptyIfNull(irsPolicy.payload().policy().getPermissions()).stream() - .flatMap(permission -> { - Constraints constraint = permission.getConstraint(); - return constraint != null ? emptyIfNull(constraint.getOr()).stream() : Stream.empty(); - }) - .anyMatch(constraint -> constraint.getRightOperand().equals(traceabilityProperties.getRightOperandSecond())); - - // Return true if both constraints exist - return firstConstraintExists && secondConstraintExists; - }) - .findFirst() - .orElse(null); - } - - - private void createMissingPolicies() { - log.info("Irs policy does not exist creating {}", traceabilityProperties.getRightOperand()); - this.irsClient.registerPolicy(); - } - - private void checkAndUpdatePolicy(IrsPolicyResponse requiredPolicy) { - if (isPolicyExpired(requiredPolicy)) { - log.info("IRS Policy {} has outdated validity updating new ttl", traceabilityProperties.getRightOperand()); - this.irsClient.deletePolicy(); - this.irsClient.registerPolicy(); - } - } - - private boolean isPolicyExpired(IrsPolicyResponse requiredPolicy) { - return traceabilityProperties.getValidUntil().isAfter(requiredPolicy.validUntil()); - } - -} diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/response/relationship/Aspect.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/response/relationship/Aspect.java index e82b37d4a3..189b0c0637 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/response/relationship/Aspect.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/response/relationship/Aspect.java @@ -22,7 +22,6 @@ import com.fasterxml.jackson.annotation.JsonValue; import java.util.List; -import java.util.Objects; public enum Aspect { BATCH("urn:samm:io.catenax.batch:3.0.0#Batch"), @@ -59,43 +58,4 @@ public static List upwardAspectsForAssetsAsBuilt() { public static List downwardAspectsForAssetsAsPlanned() { return List.of(PART_AS_PLANNED.getAspectName(), PART_SITE_INFORMATION_AS_PLANNED.getAspectName()); } - - - public static boolean isMainAspect(String aspect) { - assert Objects.nonNull(aspect); - return aspect.contains(Aspect.PART_AS_PLANNED.getAspectName()) || - aspect.contains(Aspect.SERIAL_PART.getAspectName()) || - aspect.contains(Aspect.BATCH.getAspectName()) || - aspect.contains(Aspect.JUST_IN_SEQUENCE_PART.getAspectName()); - } - - public static boolean isAsBuiltMainAspect(String aspect) { - return aspect.contains(Aspect.SERIAL_PART.getAspectName()) || - aspect.contains(Aspect.BATCH.getAspectName()) || - aspect.contains(Aspect.JUST_IN_SEQUENCE_PART.getAspectName()); - } - - public static boolean isAsPlannedMainAspect(String aspect) { - return aspect.contains(Aspect.PART_AS_PLANNED.getAspectName()); - } - - public static boolean isPartSiteInformationAsPlanned(String aspect){ - return aspect.contains(Aspect.PART_SITE_INFORMATION_AS_PLANNED.getAspectName()); - } - - public static boolean isTractionBatteryCode(String aspect){ - return aspect.contains(Aspect.TRACTION_BATTERY_CODE.getAspectName()); - } - - public static boolean isUpwardRelationshipAsBuilt(String aspect){ - return aspect.contains(Aspect.SINGLE_LEVEL_BOM_AS_BUILT.getAspectName()); - } - - public static boolean isDownwardRelationshipAsBuilt(String aspect){ - return aspect.contains(Aspect.SINGLE_LEVEL_USAGE_AS_BUILT.getAspectName()); - } - - public static boolean isUpwardRelationshipAsPlanned(String aspect){ - return aspect.contains(Aspect.SINGLE_LEVEL_BOM_AS_PLANNED.getAspectName()); - } } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfig.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfig.java index 1829b23dd2..fd4dac3939 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfig.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfig.java @@ -21,7 +21,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.traceability.assets.domain.base.PolicyRepository; +import org.eclipse.tractusx.traceability.policies.domain.PolicyRepository; import org.eclipse.tractusx.traceability.notification.application.contract.model.CreateNotificationContractRequest; import org.eclipse.tractusx.traceability.notification.application.contract.model.NotificationMethod; import org.eclipse.tractusx.traceability.notification.application.contract.model.NotificationType; @@ -58,7 +58,7 @@ public void registerIrsPolicy() { executor.execute(() -> { try { - policyRepository.createIrsPolicyIfMissing(); + policyRepository.createPolicyBasedOnAppConfig(); } catch (Exception exception) { log.error("Failed to create Irs Policies: ", exception); } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ErrorHandlingConfig.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ErrorHandlingConfig.java index 0af6bc3c41..1754be4a3b 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ErrorHandlingConfig.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/ErrorHandlingConfig.java @@ -33,6 +33,8 @@ import org.eclipse.tractusx.traceability.assets.domain.asbuilt.exception.AssetNotFoundException; import org.eclipse.tractusx.traceability.assets.domain.importpoc.exception.ImportException; import org.eclipse.tractusx.traceability.assets.domain.importpoc.exception.ImportJobNotFoundException; +import org.eclipse.tractusx.traceability.assets.domain.importpoc.exception.PolicyBadRequestException; +import org.eclipse.tractusx.traceability.assets.domain.importpoc.exception.PolicyNotFoundException; import org.eclipse.tractusx.traceability.assets.domain.importpoc.exception.PublishAssetException; import org.eclipse.tractusx.traceability.bpn.domain.model.BpnNotFoundException; import org.eclipse.tractusx.traceability.common.domain.ParseLocalDateException; @@ -44,19 +46,20 @@ import org.eclipse.tractusx.traceability.discovery.infrastructure.exception.DiscoveryFinderException; import org.eclipse.tractusx.traceability.notification.application.contract.model.CreateNotificationContractException; import org.eclipse.tractusx.traceability.notification.application.notification.validation.UpdateNotificationValidationException; -import org.eclipse.tractusx.traceability.notification.domain.notification.exception.NotificationSenderAndReceiverBPNEqualException; -import org.eclipse.tractusx.traceability.notification.domain.notification.exception.NotificationNotFoundException; -import org.eclipse.tractusx.traceability.notification.domain.notification.exception.NotificationIllegalUpdate; import org.eclipse.tractusx.traceability.notification.domain.base.exception.SendNotificationException; import org.eclipse.tractusx.traceability.notification.domain.notification.exception.InvestigationIllegalUpdate; import org.eclipse.tractusx.traceability.notification.domain.notification.exception.InvestigationNotFoundException; import org.eclipse.tractusx.traceability.notification.domain.notification.exception.InvestigationReceiverBpnMismatchException; import org.eclipse.tractusx.traceability.notification.domain.notification.exception.InvestigationStatusTransitionNotAllowed; +import org.eclipse.tractusx.traceability.notification.domain.notification.exception.NotificationIllegalUpdate; +import org.eclipse.tractusx.traceability.notification.domain.notification.exception.NotificationNotFoundException; import org.eclipse.tractusx.traceability.notification.domain.notification.exception.NotificationNotSupportedException; +import org.eclipse.tractusx.traceability.notification.domain.notification.exception.NotificationSenderAndReceiverBPNEqualException; import org.eclipse.tractusx.traceability.notification.domain.notification.exception.NotificationStatusTransitionNotAllowed; import org.eclipse.tractusx.traceability.submodel.domain.model.SubmodelNotFoundException; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.orm.jpa.JpaSystemException; @@ -66,13 +69,19 @@ import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import java.io.IOException; import java.util.NoSuchElementException; import java.util.stream.Collectors; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; + @Slf4j @RestControllerAdvice @RequiredArgsConstructor @@ -88,14 +97,51 @@ ResponseEntity handleMethodArgumentNotValidException(MethodArgume .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.joining(", ")); log.warn("handleMethodArgumentNotValidException", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) + .body(new ErrorResponse(errorMessage)); + } + + @ExceptionHandler(HttpClientErrorException.class) + ResponseEntity handleHttpClientErrorException(HttpClientErrorException exception) { + log.warn("handleHttpClientErrorException", exception); + + HttpStatusCode status = HttpStatusCode.valueOf(exception.getStatusCode().value()); + String errorMessage; + + if (status.equals(BAD_REQUEST)) { + return ResponseEntity.status(BAD_REQUEST) + .body(new ErrorResponse(exception.getMessage())); + } else if (status.equals(NOT_FOUND)) { + return ResponseEntity.status(NOT_FOUND) + .body(new ErrorResponse(exception.getMessage())); + } else { + errorMessage = exception.getMessage(); + return ResponseEntity.status(status) + .body(new ErrorResponse(errorMessage)); + } + } + + @ExceptionHandler(PolicyBadRequestException.class) + ResponseEntity handlePolicyBadRequestException(PolicyBadRequestException exception) { + log.warn("handlePolicyBadRequestException", exception); + return ResponseEntity.status(BAD_REQUEST) + .body(new ErrorResponse(exception.getMessage())); + } + + @ExceptionHandler(HttpServerErrorException.class) + public ResponseEntity handleHttpServerErrorException(HttpServerErrorException exception) { + log.warn("handleHttpServerErrorException", exception); + + HttpStatusCode status = exception.getStatusCode(); + String errorMessage = exception.getMessage(); + return ResponseEntity.status(status) .body(new ErrorResponse(errorMessage)); } @ExceptionHandler(JpaSystemException.class) ResponseEntity handleJpaSystemException(JpaSystemException exception) { log.warn("handleJpaSystemException", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse("Failed to deserialize request body.")); } @@ -107,14 +153,21 @@ ResponseEntity handleHttpMessageNotReadableException(HttpMessageN if (exception.getRootCause() instanceof NoSuchElementException) { message = ExceptionUtils.getRootCauseMessage(exception); } - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(message)); } @ExceptionHandler(AssetNotFoundException.class) ResponseEntity handleAssetNotFoundException(AssetNotFoundException exception) { log.warn("handleAssetNotFoundException", exception); - return ResponseEntity.status(HttpStatus.NOT_FOUND) + return ResponseEntity.status(NOT_FOUND) + .body(new ErrorResponse(exception.getMessage())); + } + + @ExceptionHandler(PolicyNotFoundException.class) + ResponseEntity handlePolicyNotFoundException(PolicyNotFoundException exception) { + log.warn("handlePolicyNotFoundException", exception); + return ResponseEntity.status(NOT_FOUND) .body(new ErrorResponse(exception.getMessage())); } @@ -128,63 +181,63 @@ ResponseEntity handleDiscoveryFinderException(DiscoveryFinderExce @ExceptionHandler(PublishAssetException.class) ResponseEntity handlePublishAssetException(PublishAssetException exception) { log.warn("handlePublishAssetException", exception); - return ResponseEntity.status(HttpStatus.NOT_FOUND) + return ResponseEntity.status(NOT_FOUND) .body(new ErrorResponse(exception.getMessage())); } @ExceptionHandler(ImportException.class) ResponseEntity handleImportException(ImportException exception) { log.warn("handleImportException", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @ExceptionHandler(InvestigationNotFoundException.class) ResponseEntity handleInvestigationNotFoundException(InvestigationNotFoundException exception) { log.warn("handleInvestigationNotFoundException", exception); - return ResponseEntity.status(HttpStatus.NOT_FOUND) + return ResponseEntity.status(NOT_FOUND) .body(new ErrorResponse(exception.getMessage())); } @ExceptionHandler(NotificationNotSupportedException.class) ResponseEntity handleInvestigationNotSupportedException(NotificationNotSupportedException exception) { log.warn("handleInvestigationNotSupportedException", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @ExceptionHandler(NotificationNotFoundException.class) ResponseEntity handleNotificationNotFoundException(NotificationNotFoundException exception) { log.warn("handleNotificationNotFoundException", exception); - return ResponseEntity.status(HttpStatus.NOT_FOUND) + return ResponseEntity.status(NOT_FOUND) .body(new ErrorResponse(exception.getMessage())); } @ExceptionHandler(InvestigationStatusTransitionNotAllowed.class) ResponseEntity handleInvestigationStatusTransitionNotAllowed(InvestigationStatusTransitionNotAllowed exception) { log.warn("handleInvestigationStatusTransitionNotAllowed", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @ExceptionHandler(BpnNotFoundException.class) ResponseEntity handleBpnEdcMappingNotFoundException(BpnNotFoundException exception) { log.warn("handleBpnEdcMappingNotFoundException", exception); - return ResponseEntity.status(HttpStatus.NOT_FOUND) + return ResponseEntity.status(NOT_FOUND) .body(new ErrorResponse(exception.getMessage())); } @ExceptionHandler(InvestigationReceiverBpnMismatchException.class) ResponseEntity handleInvestigationReceiverBpnMismatchException(InvestigationReceiverBpnMismatchException exception) { log.warn("handleInvestigationReceiverBpnMismatchException", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @ExceptionHandler(NotificationSenderAndReceiverBPNEqualException.class) ResponseEntity handleNotificationSenderAndReceiverBPNEqualException(NotificationSenderAndReceiverBPNEqualException exception) { log.warn("handleNotificationSenderAndReceiverBPNEqualException", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @@ -198,7 +251,7 @@ ResponseEntity handleSendNotificationException(SendNotificationEx @ExceptionHandler(ValidationException.class) ResponseEntity handleValidationException(ValidationException exception) { log.warn("handleValidationException", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @@ -219,14 +272,14 @@ ResponseEntity handleAlertIllegalUpdate(NotificationIllegalUpdate @ExceptionHandler(IllegalArgumentException.class) ResponseEntity handleIllegalArgumentException(IllegalArgumentException exception) { log.warn("handleIllegalArgumentException", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @ExceptionHandler(ParseLocalDateException.class) ResponseEntity handleParseLocalDateException(ParseLocalDateException exception) { log.warn("handleParseLocalDateException", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @@ -254,28 +307,28 @@ ResponseEntity handleAuthenticationCredentialsNotFoundException(A @ExceptionHandler(NotificationStatusTransitionNotAllowed.class) ResponseEntity handleNotificationStatusTransitionNotAllowed(NotificationStatusTransitionNotAllowed exception) { log.warn("handleNotificationStatusTransitionNotAllowed", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @ExceptionHandler(UpdateNotificationValidationException.class) ResponseEntity handleUpdateNotificationValidationException(UpdateNotificationValidationException exception) { log.warn("handleUpdateNotificationValidationException", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @ExceptionHandler(InvalidFilterException.class) ResponseEntity handleInvalidFilterException(InvalidFilterException exception) { log.warn("handleInvalidFilterException", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @ExceptionHandler(UnsupportedSearchCriteriaFieldException.class) ResponseEntity handleUnsupportedSearchCriteriaFieldException(UnsupportedSearchCriteriaFieldException exception) { log.warn("handleInvalidFilterException", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @@ -316,7 +369,7 @@ public void onAuthenticationFailure(HttpServletRequest request, HttpServletRespo public ResponseEntity handleInvalidSortException(final InvalidSortException exception) { log.error("InvalidSortException exception", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @@ -325,7 +378,7 @@ ResponseEntity handleSubmodelNotFoundException(SubmodelNotFoundEx String errorMessage = exception .getMessage(); log.warn("handleSubmodelNotFoundException", exception); - return ResponseEntity.status(HttpStatus.NOT_FOUND) + return ResponseEntity.status(NOT_FOUND) .body(new ErrorResponse(errorMessage)); } @@ -333,7 +386,7 @@ ResponseEntity handleSubmodelNotFoundException(SubmodelNotFoundEx public ResponseEntity handleMethodArgumentTypeMismatchException(final MethodArgumentTypeMismatchException exception) { log.error("MethodArgumentTypeMismatchException exception", exception); - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(BAD_REQUEST) .body(new ErrorResponse(exception.getMessage())); } @@ -341,7 +394,7 @@ public ResponseEntity handleMethodArgumentTypeMismatchException(f public ResponseEntity handleImportJobNotFoundException(final ImportJobNotFoundException exception) { log.error("ImportJobNotFoundException exception", exception); - return ResponseEntity.status(HttpStatus.NOT_FOUND) + return ResponseEntity.status(NOT_FOUND) .body(new ErrorResponse(exception.getMessage())); } @@ -349,7 +402,7 @@ public ResponseEntity handleImportJobNotFoundException(final Impo public ResponseEntity handleContractException(final ContractException exception) { log.error("Contract exception", exception); - return ResponseEntity.status(HttpStatus.NOT_FOUND) + return ResponseEntity.status(NOT_FOUND) .body(new ErrorResponse(exception.getMessage())); } } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/PolicyStartUpConfig.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/PolicyStartUpConfig.java index 733cd41f4b..67aff58f74 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/PolicyStartUpConfig.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/PolicyStartUpConfig.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; @@ -33,8 +34,8 @@ import org.eclipse.tractusx.irs.edc.client.policy.Permission; import org.eclipse.tractusx.irs.edc.client.policy.Policy; import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; -import org.eclipse.tractusx.traceability.assets.domain.base.PolicyRepository; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; +import org.eclipse.tractusx.traceability.policies.domain.PolicyRepository; +import policies.response.IrsPolicyResponse; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; import org.jetbrains.annotations.NotNull; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -49,7 +50,9 @@ import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; @Configuration @ConfigurationPropertiesScan(basePackages = "org.eclipse.tractusx.traceability.*") @@ -72,6 +75,7 @@ public class PolicyStartUpConfig { public void registerDecentralRegistryPermissions() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); List acceptedPolicy = buildAcceptedPolicies(); defaultAcceptedPoliciesProvider.addAcceptedPolicies(acceptedPolicy); log.info("Successfully added permission to irs client lib provider: {}", mapper.writeValueAsString(acceptedPolicy)); @@ -91,13 +95,21 @@ private List buildAcceptedPolicies() { } private List createIrsAcceptedPolicies() { + // Get the policies map from the repository + Map> policiesMap = policyRepository.getPolicies(); - List irsPolicyResponse = policyRepository.getPolicies(); - List irsPolicies = irsPolicyResponse.stream().map(response -> { + // Flatten the map into a list of IrsPolicyResponse objects + List irsPolicyResponses = policiesMap.values().stream() + .flatMap(List::stream) + .toList(); + + // Map the IrsPolicyResponse objects to AcceptedPolicy objects + List irsPolicies = irsPolicyResponses.stream().map(response -> { Policy policy = new Policy(response.payload().policyId(), response.payload().policy().getCreatedOn(), response.validUntil(), response.payload().policy().getPermissions()); return new AcceptedPolicy(policy, response.validUntil()); }).toList(); + // Return the list of AcceptedPolicy objects return new ArrayList<>(irsPolicies); } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/RestTemplateConfiguration.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/RestTemplateConfiguration.java index 77f78a8d88..32b316b61e 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/RestTemplateConfiguration.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/RestTemplateConfiguration.java @@ -21,11 +21,14 @@ package org.eclipse.tractusx.traceability.common.config; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.eclipse.tractusx.traceability.common.properties.BpdmProperties; import org.eclipse.tractusx.traceability.common.properties.EdcProperties; import org.eclipse.tractusx.traceability.common.properties.FeignDefaultProperties; @@ -37,6 +40,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager; @@ -44,11 +51,13 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestTemplate; import java.time.Duration; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Collections; import java.util.List; @Configuration @@ -64,7 +73,7 @@ public class RestTemplateConfiguration { public static final String IRS_REGULAR_TEMPLATE = "irsRegularTemplate"; public static final String SUBMODEL_REST_TEMPLATE = "submodelRestTemplate"; public static final String DIGITAL_TWIN_REGISTRY_REST_TEMPLATE = "digitalTwinRegistryRestTemplate"; - public static final String EDC_CLIENT_REST_TEMPLATE= "edcClientRestTemplate"; + public static final String EDC_CLIENT_REST_TEMPLATE = "edcClientRestTemplate"; public static final String BPDM_CLIENT_REST_TEMPLATE = "bpdmClientRestTemplate"; private static final String EDC_API_KEY_HEADER_NAME = "X-Api-Key"; @@ -72,6 +81,7 @@ public class RestTemplateConfiguration { private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService; private final ClientRegistrationRepository clientRegistrationRepository; + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"); /* RestTemplate used by trace x for the resolution of manufacturer names by BPN.*/ @@ -196,13 +206,14 @@ private RestTemplateBuilder oAuthRestTemplate(final RestTemplateBuilder restTemp private List> customMessageConverters() { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + JavaTimeModule javaTimeModule = new JavaTimeModule(); converter.setObjectMapper(JsonMapper.builder() .configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, true) .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true) .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .build() - .registerModules(new JavaTimeModule())); + .registerModules(javaTimeModule)); // Add the custom converter to the list of message converters List> converters = new ArrayList<>(); diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/notification/domain/base/service/NotificationPublisherService.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/notification/domain/base/service/NotificationPublisherService.java index eb9e343a34..8a1a577d9a 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/notification/domain/base/service/NotificationPublisherService.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/notification/domain/base/service/NotificationPublisherService.java @@ -64,8 +64,12 @@ public Notification startNotification(StartNotification startNotification) { } private void createMessages(StartNotification startNotification, BPN applicationBPN, Notification notification, AssetAsBuiltRepository assetAsBuiltRepository) { - Map> assetsAsBuiltBPNMap = assetAsBuiltRepository.getAssetsById(startNotification.getAffectedPartIds()).stream().collect(groupingBy(AssetBase::getManufacturerId)); - assetsAsBuiltBPNMap + Map> assetsAsBuiltBPNMap = + assetAsBuiltRepository + .getAssetsById(startNotification.getAffectedPartIds()) + .stream() + .filter(asset -> Objects.nonNull(asset.getManufacturerId())) + .collect(groupingBy(AssetBase::getManufacturerId)); assetsAsBuiltBPNMap .entrySet() .stream() .map(it -> { diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/notification/domain/contract/EdcNotificationContractService.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/notification/domain/contract/EdcNotificationContractService.java index 156ac3a013..d2acc826a1 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/notification/domain/contract/EdcNotificationContractService.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/notification/domain/contract/EdcNotificationContractService.java @@ -21,7 +21,7 @@ ********************************************************************************/ package org.eclipse.tractusx.traceability.notification.domain.contract; -import assets.importpoc.PolicyResponse; +import policies.response.PolicyResponse; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.irs.edc.client.asset.EdcAssetService; @@ -35,8 +35,8 @@ import org.eclipse.tractusx.irs.edc.client.policy.model.exception.DeleteEdcPolicyDefinitionException; import org.eclipse.tractusx.irs.edc.client.policy.model.exception.EdcPolicyDefinitionAlreadyExists; import org.eclipse.tractusx.irs.edc.client.policy.service.EdcPolicyDefinitionService; -import org.eclipse.tractusx.traceability.assets.application.importpoc.PolicyService; -import org.eclipse.tractusx.traceability.assets.application.importpoc.mapper.PolicyMapper; +import org.eclipse.tractusx.traceability.policies.application.service.PolicyService; +import org.eclipse.tractusx.traceability.policies.application.mapper.PolicyMapper; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; import org.eclipse.tractusx.traceability.notification.application.contract.model.CreateNotificationContractException; import org.eclipse.tractusx.traceability.notification.application.contract.model.CreateNotificationContractRequest; diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/mapper/PolicyMapper.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/application/mapper/PolicyMapper.java similarity index 94% rename from tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/mapper/PolicyMapper.java rename to tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/application/mapper/PolicyMapper.java index 0afee39a89..5ebb14424b 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/application/importpoc/mapper/PolicyMapper.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/application/mapper/PolicyMapper.java @@ -16,12 +16,12 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.traceability.assets.application.importpoc.mapper; +package org.eclipse.tractusx.traceability.policies.application.mapper; -import assets.importpoc.ConstraintResponse; -import assets.importpoc.ConstraintsResponse; -import assets.importpoc.PermissionResponse; -import assets.importpoc.PolicyResponse; +import policies.response.ConstraintResponse; +import policies.response.ConstraintsResponse; +import policies.response.PermissionResponse; +import policies.response.PolicyResponse; import lombok.experimental.UtilityClass; import org.eclipse.tractusx.irs.edc.client.asset.model.Context; import org.eclipse.tractusx.irs.edc.client.contract.model.EdcOperator; diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/application/rest/PolicyController.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/application/rest/PolicyController.java new file mode 100644 index 0000000000..cc4596d026 --- /dev/null +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/application/rest/PolicyController.java @@ -0,0 +1,352 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.traceability.policies.application.rest; + + +import assets.importpoc.ErrorResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.traceability.policies.application.service.PolicyService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import policies.request.RegisterPolicyRequest; +import policies.request.UpdatePolicyRequest; +import policies.response.CreatePolicyResponse; +import policies.response.IrsPolicyResponse; +import policies.response.PolicyResponse; + +import java.util.List; +import java.util.Map; + +@Slf4j +@RequiredArgsConstructor +@RestController +@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_SUPERVISOR')") +@Tag(name = "Policies") +@RequestMapping(path = "/policies") +public class PolicyController { + + private final PolicyService policyService; + + @Operation(operationId = "policy", + summary = "Get all policies ", + tags = {"Policies"}, + description = "The endpoint returns all policies .", + security = @SecurityRequirement(name = "oAuth2", scopes = "profile email")) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Returns the policies", + content = {@Content( + mediaType = "application/json", + schema = @Schema(implementation = IrsPolicyResponse.class))}), + @ApiResponse( + responseCode = "400", + description = "Bad request.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "401", + description = "Authorization failed.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + + @ApiResponse( + responseCode = "403", + description = "Forbidden.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "404", + description = "Not found.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "415", + description = "Unsupported media type", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "429", + description = "Too many requests.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class)))}) + @GetMapping() + public Map> getPolicies() { + return policyService.getIrsPolicies(); + } + + @Operation(operationId = "getPolicyById", + summary = "Gets policy by id", + tags = {"Policies"}, + description = "The endpoint returns policy by id.", + security = @SecurityRequirement(name = "oAuth2", scopes = "profile email")) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Returns the policies", + content = {@Content( + mediaType = "application/json", + schema = @Schema(implementation = PolicyResponse.class))}), + @ApiResponse(responseCode = "200", description = "OK."), + @ApiResponse( + responseCode = "400", + description = "Bad request.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "401", + description = "Authorization failed.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + + @ApiResponse( + responseCode = "403", + description = "Forbidden.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + + @ApiResponse( + responseCode = "404", + description = "Not found.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "415", + description = "Unsupported media type", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "429", + description = "Too many requests.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class)))}) + @GetMapping("/{policyId}") + public PolicyResponse getPolicyById(@PathVariable("policyId") String policyId) { + return policyService.getPolicy(policyId); + } + + @Operation(operationId = "createPolicy", + summary = "Create a policy ", + tags = {"Policies"}, + description = "The endpoint creates a policy.", + security = @SecurityRequirement(name = "oAuth2", scopes = "profile email")) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Returns the policies", + content = {@Content( + mediaType = "application/json", + schema = @Schema(implementation = CreatePolicyResponse.class))}), + @ApiResponse( + responseCode = "400", + description = "Bad request.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "401", + description = "Authorization failed.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + + @ApiResponse( + responseCode = "403", + description = "Forbidden.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "404", + description = "Not found.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "415", + description = "Unsupported media type", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "429", + description = "Too many requests.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class)))}) + @PostMapping() + public CreatePolicyResponse createPolicy(@RequestBody RegisterPolicyRequest registerPolicyRequest) { + return policyService.createPolicy(registerPolicyRequest); + } + + @Operation(operationId = "updatePolicy", + summary = "Updates policies ", + tags = {"Policies"}, + description = "The endpoint updates policies.", + security = @SecurityRequirement(name = "oAuth2", scopes = "profile email")) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Update successful", + content = {@Content( + mediaType = "application/json", + schema = @Schema())}), + @ApiResponse( + responseCode = "400", + description = "Bad request.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "401", + description = "Authorization failed.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + + @ApiResponse( + responseCode = "403", + description = "Forbidden.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "404", + description = "Not found.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "415", + description = "Unsupported media type", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "429", + description = "Too many requests.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class)))}) + @PutMapping() + public void updatePolicy(@RequestBody UpdatePolicyRequest updatePolicyRequest) { + policyService.updatePolicy(updatePolicyRequest); + } + + @Operation(operationId = "deletePolicy", + summary = "Deletes a policy ", + tags = {"Policies"}, + description = "The endpoint deletes policies.", + security = @SecurityRequirement(name = "oAuth2", scopes = "profile email")) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Deletion successful", + content = {@Content( + mediaType = "application/json", + schema = @Schema())}), + @ApiResponse( + responseCode = "400", + description = "Bad request.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "401", + description = "Authorization failed.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + + @ApiResponse( + responseCode = "403", + description = "Forbidden.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "404", + description = "Not found.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "415", + description = "Unsupported media type", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "429", + description = "Too many requests.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse( + responseCode = "500", + description = "Internal server error.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class)))}) + @DeleteMapping("/{policyId}") + public void deletePolicy(@PathVariable("policyId") String policyId) { + policyService.deletePolicy(policyId); + } +} diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/application/service/PolicyService.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/application/service/PolicyService.java new file mode 100644 index 0000000000..eda69298a5 --- /dev/null +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/application/service/PolicyService.java @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.traceability.policies.application.service; + +import policies.request.RegisterPolicyRequest; +import policies.request.UpdatePolicyRequest; +import policies.response.CreatePolicyResponse; +import policies.response.IrsPolicyResponse; +import policies.response.PolicyResponse; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public interface PolicyService { + List getPolicies(); + + Map> getIrsPolicies(); + PolicyResponse getPolicy(String id); + + Optional getFirstPolicyMatchingApplicationConstraint(); + + CreatePolicyResponse createPolicy(RegisterPolicyRequest registerPolicyRequest); + + void deletePolicy(String id); + + void updatePolicy(UpdatePolicyRequest updatePolicyRequest); +} diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/domain/PolicyRepository.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/domain/PolicyRepository.java new file mode 100644 index 0000000000..53a9f7e0bb --- /dev/null +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/domain/PolicyRepository.java @@ -0,0 +1,37 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.traceability.policies.domain; + +import policies.response.CreatePolicyResponse; +import policies.response.IrsPolicyResponse; +import policies.request.RegisterPolicyRequest; +import policies.request.UpdatePolicyRequest; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public interface PolicyRepository { + Map> getPolicies(); + Map> getPolicy(String policyId); + void createPolicyBasedOnAppConfig(); + void deletePolicy(String policyId); + void updatePolicy(UpdatePolicyRequest updatePolicyRequest); + CreatePolicyResponse createPolicy(RegisterPolicyRequest registerPolicyRequest); +} diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImpl.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/domain/PolicyServiceImpl.java similarity index 64% rename from tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImpl.java rename to tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/domain/PolicyServiceImpl.java index 660f87b85d..59ade1840a 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImpl.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/domain/PolicyServiceImpl.java @@ -16,26 +16,30 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.traceability.assets.domain.importpoc.service; +package org.eclipse.tractusx.traceability.policies.domain; -import assets.importpoc.PolicyResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.traceability.assets.application.importpoc.PolicyService; -import org.eclipse.tractusx.traceability.assets.domain.base.PolicyRepository; import org.eclipse.tractusx.traceability.assets.domain.importpoc.exception.PolicyNotFoundException; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; +import org.eclipse.tractusx.traceability.policies.application.service.PolicyService; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; +import policies.request.RegisterPolicyRequest; +import policies.request.UpdatePolicyRequest; +import policies.response.CreatePolicyResponse; +import policies.response.IrsPolicyResponse; +import policies.response.PolicyResponse; import java.util.AbstractMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; import static org.apache.commons.collections4.ListUtils.emptyIfNull; +import static policies.response.IrsPolicyResponse.toResponse; @Slf4j @RequiredArgsConstructor @@ -46,21 +50,31 @@ public class PolicyServiceImpl implements PolicyService { private final TraceabilityProperties traceabilityProperties; @Override - public List getAllPolicies() { - return IrsPolicyResponse.toResponse(getAcceptedPoliciesOrEmptyList()); + public Map> getIrsPolicies() { + return policyRepository.getPolicies(); } @Override - public PolicyResponse getPolicyById(String id) { - return getAcceptedPoliciesOrEmptyList().stream() - .filter(policy -> policy.payload().policy().getPolicyId().equals(id)).findFirst() - .map(IrsPolicyResponse::toResponse) + public List getPolicies() { + Map> policies = policyRepository.getPolicies(); + return toResponse(policies); + } + + @Override + public PolicyResponse getPolicy(String id) { + Map> policies = policyRepository.getPolicy(id); + + // Find the first entry with a present policy + return policies.entrySet().stream() + .filter(entry -> entry.getValue().isPresent()) + .findFirst() + .map(entry -> toResponse(entry.getValue().get(), entry.getKey())) .orElseThrow(() -> new PolicyNotFoundException("Policy with id: %s not found.".formatted(id))); } @Override public Optional getFirstPolicyMatchingApplicationConstraint() { - Optional policyId = getAllPolicies().stream() + Optional policyId = getPolicies().stream() .flatMap(policyResponse -> policyResponse.permissions().stream() .flatMap(permissionResponse -> Stream.concat( permissionResponse.constraints().and().stream(), @@ -73,12 +87,23 @@ public Optional getFirstPolicyMatchingApplicationConstraint() { }) .map(Map.Entry::getKey) .findFirst(); - return policyId.map(this::getPolicyById); + return policyId.map(this::getPolicy); + } + + @Override + public CreatePolicyResponse createPolicy(RegisterPolicyRequest registerPolicyRequest) { + return policyRepository.createPolicy(registerPolicyRequest); } + @Override + public void deletePolicy(String id) { + policyRepository.deletePolicy(id); + } - @NotNull - private List getAcceptedPoliciesOrEmptyList() { - return emptyIfNull(policyRepository.getPolicies()); + @Override + public void updatePolicy(UpdatePolicyRequest updatePolicyRequest) { + policyRepository.updatePolicy(updatePolicyRequest); } + + } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/IrsClient.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/infrastructure/PolicyClient.java similarity index 65% rename from tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/IrsClient.java rename to tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/infrastructure/PolicyClient.java index 4899fe7843..d0de0fe191 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/IrsClient.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/infrastructure/PolicyClient.java @@ -16,7 +16,7 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.traceability.assets.infrastructure.base.irs; +package org.eclipse.tractusx.traceability.policies.infrastructure; import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.irs.edc.client.policy.Constraint; @@ -26,14 +26,7 @@ import org.eclipse.tractusx.irs.edc.client.policy.Permission; import org.eclipse.tractusx.irs.edc.client.policy.Policy; import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.RegisterJobRequest; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.RegisterPolicyRequest; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Context; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IRSResponse; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Payload; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; -import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.ParameterizedTypeReference; @@ -41,50 +34,68 @@ import org.springframework.http.HttpMethod; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; +import policies.request.Context; +import policies.request.Payload; +import policies.request.RegisterPolicyRequest; +import policies.request.UpdatePolicyRequest; +import policies.response.CreatePolicyResponse; +import policies.response.IrsPolicyResponse; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; import static org.eclipse.tractusx.traceability.common.config.RestTemplateConfiguration.IRS_ADMIN_TEMPLATE; -import static org.eclipse.tractusx.traceability.common.config.RestTemplateConfiguration.IRS_REGULAR_TEMPLATE; @Slf4j @Component -public class IrsClient { - +public class PolicyClient { private final RestTemplate irsAdminTemplate; - - private final RestTemplate irsRegularTemplate; - private final TraceabilityProperties traceabilityProperties; @Value("${traceability.irsPoliciesPath}") String policiesPath = null; - public IrsClient(@Qualifier(IRS_ADMIN_TEMPLATE) RestTemplate irsAdminTemplate, - @Qualifier(IRS_REGULAR_TEMPLATE) RestTemplate irsRegularTemplate, - TraceabilityProperties traceabilityProperties) { + public PolicyClient(@Qualifier(IRS_ADMIN_TEMPLATE) RestTemplate irsAdminTemplate, + TraceabilityProperties traceabilityProperties) { this.irsAdminTemplate = irsAdminTemplate; - this.irsRegularTemplate = irsRegularTemplate; this.traceabilityProperties = traceabilityProperties; } - public List getPolicies() { + public Map> getPolicies() { Map> body = irsAdminTemplate.exchange(policiesPath, HttpMethod.GET, null, new ParameterizedTypeReference>>() { }).getBody(); - return body == null ? List.of() : body.values().stream().flatMap(List::stream).toList(); + + if (body != null) { + body.forEach((key, valueList) -> { + log.info("Key: {}", key); + valueList.forEach(value -> log.info("Policy: {}", value)); + }); + } else { + log.info("No policies retrieved from IRS Policy Store"); + } + return body; } - public void deletePolicy() { - irsAdminTemplate.exchange(policiesPath + "/" + traceabilityProperties.getRightOperand(), HttpMethod.DELETE, null, new ParameterizedTypeReference<>() { + public void deletePolicy(String policyId) { + irsAdminTemplate.exchange(policiesPath + "/" + policyId, HttpMethod.DELETE, null, new ParameterizedTypeReference<>() { }); } - public void registerPolicy() { + public void updatePolicy(UpdatePolicyRequest updatePolicyRequest) { + irsAdminTemplate.exchange(policiesPath, HttpMethod.PUT, new HttpEntity<>(updatePolicyRequest), new ParameterizedTypeReference<>() { + }); + } + + public CreatePolicyResponse createPolicy(RegisterPolicyRequest registerPolicyRequest) { + return irsAdminTemplate.exchange(policiesPath, HttpMethod.POST, new HttpEntity<>(registerPolicyRequest), CreatePolicyResponse.class).getBody(); + } + + public void createPolicyFromAppConfig() { OffsetDateTime validUntil = traceabilityProperties.getValidUntil(); Context context = Context.getDefault(); String policyId = UUID.randomUUID().toString(); @@ -104,18 +115,7 @@ public void registerPolicy() { Policy policy = new Policy(policyId, Instant.now().atOffset(ZoneOffset.UTC), validUntil, List.of(permission)); Payload payload = new Payload(context, policyId, policy); - RegisterPolicyRequest registerPolicyRequest = new RegisterPolicyRequest(validUntil.toInstant(), payload); + RegisterPolicyRequest registerPolicyRequest = new RegisterPolicyRequest(validUntil.toInstant(), traceabilityProperties.getBpn().value(), payload); irsAdminTemplate.exchange(policiesPath, HttpMethod.POST, new HttpEntity<>(registerPolicyRequest), Void.class); } - - public void registerJob(RegisterJobRequest registerJobRequest) { - irsRegularTemplate.exchange("/irs/jobs", HttpMethod.POST, new HttpEntity<>(registerJobRequest), Void.class); - } - - @Nullable - public IRSResponse getIrsJobDetailResponse(String jobId) { - return irsRegularTemplate.exchange("/irs/jobs/" + jobId, HttpMethod.GET, null, new ParameterizedTypeReference() { - }).getBody(); - } - } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/infrastructure/PolicyRepositoryImpl.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/infrastructure/PolicyRepositoryImpl.java new file mode 100644 index 0000000000..0b75dee54a --- /dev/null +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/policies/infrastructure/PolicyRepositoryImpl.java @@ -0,0 +1,163 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package org.eclipse.tractusx.traceability.policies.infrastructure; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.irs.edc.client.policy.Constraint; +import org.eclipse.tractusx.irs.edc.client.policy.Constraints; +import org.eclipse.tractusx.irs.edc.client.policy.Permission; +import org.eclipse.tractusx.traceability.policies.domain.PolicyRepository; +import policies.response.CreatePolicyResponse; +import policies.response.IrsPolicyResponse; +import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; +import org.springframework.stereotype.Service; +import policies.request.RegisterPolicyRequest; +import policies.request.UpdatePolicyRequest; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import static org.apache.commons.collections4.ListUtils.emptyIfNull; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PolicyRepositoryImpl implements PolicyRepository { + + private final PolicyClient policyClient; + private final TraceabilityProperties traceabilityProperties; + + @Override + public Map> getPolicies() { + return policyClient.getPolicies(); + } + + @Override + public Map> getPolicy(String policyId) { + Map> result = new HashMap<>(); + + getPolicies().forEach((key, value) -> { + Optional policyResponse = value.stream() + .filter(irsPolicyResponse -> irsPolicyResponse.payload().policyId().equals(policyId)) + .findFirst(); + if (policyResponse.isPresent()) { + result.put(key, policyResponse); + } + }); + + return result; + } + + + + @Override + public void createPolicyBasedOnAppConfig() { + log.info("Check if irs policy exists"); + final Map> irsPolicies = this.policyClient.getPolicies(); + final List irsPoliciesIds = irsPolicies.values().stream() + .flatMap(List::stream) + .map(irsPolicyResponse -> irsPolicyResponse.payload().policyId()) + .toList(); log.info("Irs has following policies: {}", irsPoliciesIds); + + log.info("Required constraints - 2 -"); + log.info("First constraint requirements: leftOperand {} operator {} and rightOperand {}", traceabilityProperties.getLeftOperand(), traceabilityProperties.getOperatorType(), traceabilityProperties.getRightOperand()); + log.info("Second constraint requirements: leftOperand {} operator {} and rightOperand {}", traceabilityProperties.getLeftOperandSecond(), traceabilityProperties.getOperatorTypeSecond(), traceabilityProperties.getRightOperandSecond()); + + IrsPolicyResponse matchingPolicy = findMatchingPolicy(irsPolicies); + + if (matchingPolicy == null) { + createMissingPolicies(); + } else { + checkAndUpdatePolicy(matchingPolicy); + } + } + + @Override + public void deletePolicy(String policyId) { + this.policyClient.deletePolicy(policyId); + } + + @Override + public void updatePolicy(UpdatePolicyRequest updatePolicyRequest) { + this.policyClient.updatePolicy(updatePolicyRequest); + } + + @Override + public CreatePolicyResponse createPolicy(RegisterPolicyRequest registerPolicyRequest) { + return this.policyClient.createPolicy(registerPolicyRequest); + } + + + + private IrsPolicyResponse findMatchingPolicy(Map> irsPolicies) { + return irsPolicies.values().stream() + .flatMap(List::stream) + .filter(this::checkConstraints) + .findFirst() + .orElse(null); + } + + private boolean checkConstraints(IrsPolicyResponse irsPolicy) { + boolean firstConstraintExists = checkConstraint(irsPolicy, traceabilityProperties.getRightOperand()); + boolean secondConstraintExists = checkConstraint(irsPolicy, traceabilityProperties.getRightOperandSecond()); + return firstConstraintExists && secondConstraintExists; + } + + private boolean checkConstraint(IrsPolicyResponse irsPolicy, String rightOperand) { + return emptyIfNull(irsPolicy.payload().policy().getPermissions()).stream() + .flatMap(this::getConstraintsStream) + .anyMatch(constraint -> constraint.getRightOperand().equals(rightOperand)); + } + + private Stream getConstraintsStream(Permission permission) { + Constraints constraint = permission.getConstraint(); + if (constraint == null) { + return Stream.empty(); + } + return Stream.concat( + emptyIfNull(constraint.getAnd()).stream(), + emptyIfNull(constraint.getOr()).stream() + ); + } + + + + + private void createMissingPolicies() { + log.info("Irs policy does not exist creating {}", traceabilityProperties.getRightOperand()); + this.policyClient.createPolicyFromAppConfig(); + } + + private void checkAndUpdatePolicy(IrsPolicyResponse requiredPolicy) { + if (isPolicyExpired(requiredPolicy)) { + log.info("IRS Policy {} has outdated validity updating new ttl", traceabilityProperties.getRightOperand()); + this.policyClient.deletePolicy(traceabilityProperties.getRightOperand()); + this.policyClient.createPolicyFromAppConfig(); + } + } + + private boolean isPolicyExpired(IrsPolicyResponse requiredPolicy) { + return traceabilityProperties.getValidUntil().isAfter(requiredPolicy.validUntil()); + } + +} diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/JsonSchemaTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/JsonSchemaTest.java index 1aa4c1ea59..d707d4d111 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/JsonSchemaTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/JsonSchemaTest.java @@ -31,13 +31,16 @@ import org.eclipse.tractusx.traceability.assets.application.importpoc.validation.exception.JsonFileProcessingException; import org.eclipse.tractusx.traceability.assets.application.importpoc.validation.exception.NotSupportedSchemaException; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Value; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.InputStream; +import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; @@ -45,16 +48,17 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import static java.util.Objects.isNull; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; @Slf4j public class JsonSchemaTest { - - public static final Map SUPPORTED_SCHEMA_VALIDATION = Map.ofEntries( Map.entry("urn:samm:io.catenax.batch:3.0.0#Batch", "/schema/semantichub/Batch_3.0.0-schema.json"), Map.entry("urn:samm:io.catenax.just_in_sequence_part:3.0.0#JustInSequencePart", "/schema/semantichub/JustInSequencePart_3.0.0-schema.json"), @@ -68,29 +72,74 @@ public class JsonSchemaTest { ); private final JsonSchemaFactory factory = JsonSchemaFactory.byDefault(); - @Value("") @Test - public void test() throws IOException { - - InputStream file = JsonSchemaTest.class.getResourceAsStream("/testdata/CX_Testdata_MessagingTest_v0.0.13.json"); - // Convert the file to a MockMultipartFile - MockMultipartFile multipartFile = new MockMultipartFile( - "file", // Parameter name in the multipart request - "import-request.json", // Original file name - "application/json", // Content type - file - ); - - List strings = isValid(multipartFile); - for (String string : strings) { - log.info(string); - } + public void testLatestJsonSchema() { + try { + String highestVersionFile = findHighestVersionFile(); + log.info("File Version: " + highestVersionFile); + InputStream file = JsonSchemaTest.class.getResourceAsStream("/testdata/jsonfiles/" + highestVersionFile); + + MockMultipartFile multipartFile = new MockMultipartFile( + "file", // Parameter name in the multipart request + "import-request.json", // Original file name + "application/json", // Content type + file + ); + + List errors = isValid(multipartFile); + for (String string : errors) { + log.info(string); + } + assertEquals(1, errors.size()); + } catch (Exception e) { + log.error("Exception encountered", e); + fail("Exception encountered: " + e.getMessage()); + } - assertEquals(0, strings.size()); + } + private String findHighestVersionFile() throws URISyntaxException, IOException { + Path dir = Path.of(JsonSchemaTest.class.getResource("/testdata/jsonfiles").toURI()); + Pattern pattern = Pattern.compile("CX_Testdata_MessagingTest_v(\\d+\\.\\d+\\.\\d+)\\.json"); + + String highestVersionFile = null; + String highestVersion = null; + + try (DirectoryStream stream = Files.newDirectoryStream(dir, "*.json")) { + for (Path entry : stream) { + Matcher matcher = pattern.matcher(entry.getFileName().toString()); + if (matcher.matches()) { + String version = matcher.group(1); + if (highestVersion == null || compareVersions(version, highestVersion) > 0) { + highestVersion = version; + highestVersionFile = entry.getFileName().toString(); + } + } + } + } + return highestVersionFile; } + private int compareVersions(String v1, String v2) { + String[] parts1 = v1.split("\\."); + String[] parts2 = v2.split("\\."); + + try { + for (int i = 0; i < parts1.length; i++) { + int part1 = Integer.parseInt(parts1[i]); + int part2 = Integer.parseInt(parts2[i]); + + if (part1 != part2) { + return Integer.compare(part1, part2); + } + } + } catch (NumberFormatException e) { + log.info("Error:" + e); + } + + return 0; + } public List isValid(MultipartFile file) { if (file == null || file.isEmpty()) { return List.of(); diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImplTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImplTest.java index 12ac9dc162..c3809e5b1a 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImplTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/domain/importpoc/service/PolicyServiceImplTest.java @@ -18,11 +18,11 @@ ********************************************************************************/ package org.eclipse.tractusx.traceability.assets.domain.importpoc.service; - -import assets.importpoc.PolicyResponse; -import org.eclipse.tractusx.traceability.assets.domain.base.PolicyRepository; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; +import policies.response.PolicyResponse; +import org.eclipse.tractusx.traceability.policies.domain.PolicyRepository; +import policies.response.IrsPolicyResponse; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; +import org.eclipse.tractusx.traceability.policies.domain.PolicyServiceImpl; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -30,13 +30,16 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.time.OffsetDateTime; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.traceability.testdata.PolicyTestDataFactory.createIrsPolicyResponse; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -50,17 +53,16 @@ class PolicyServiceImplTest { private TraceabilityProperties traceabilityProperties; @Test - void testGetPolicyByID() { - - + void testGetPolicy() { // GIVEN String policyId = "policy123"; OffsetDateTime createdOn = OffsetDateTime.parse("2023-07-03T16:01:05.309Z"); - List acceptedPolicies = List.of(createIrsPolicyResponse(policyId, createdOn, "", "", "", "")); + IrsPolicyResponse irsPolicyResponse = createIrsPolicyResponse(policyId, createdOn, "", "", "", ""); + Map> acceptedPolicies = Map.of("key", List.of(irsPolicyResponse)); // WHEN when(policyRepository.getPolicies()).thenReturn(acceptedPolicies); - List allPolicies = policyService.getAllPolicies(); + List allPolicies = policyService.getPolicies(); // THEN assertNotNull(allPolicies); @@ -75,38 +77,49 @@ void getPolicyByConstraintRightOperand() { IrsPolicyResponse secondPolicyResponse = createIrsPolicyResponse("test2", OffsetDateTime.now(), "my-left-constraint2", "", "", "my-constraint2"); IrsPolicyResponse thirdPolicyResponse = createIrsPolicyResponse("test3", OffsetDateTime.now(), "my-left-constraint3", "", "", "my-constraint3"); IrsPolicyResponse fourthPolicyResponse = createIrsPolicyResponse("test4", OffsetDateTime.now(), "my-left-constraint4", "", "my-constraint4", ""); - List policyResponseList = List.of(firstPolicyResponse, secondPolicyResponse, thirdPolicyResponse, fourthPolicyResponse); - when(policyRepository.getPolicies()).thenReturn(policyResponseList); + Map> policyResponseMap = Map.of( + "key1", List.of(firstPolicyResponse), + "key2", List.of(secondPolicyResponse), + "key3", List.of(thirdPolicyResponse), + "key4", List.of(fourthPolicyResponse) + ); + when(policyRepository.getPolicies()).thenReturn(policyResponseMap); + Map> policyMap = new HashMap<>(); + policyMap.put("bpn123", Optional.of(fourthPolicyResponse)); + + when(policyRepository.getPolicy(anyString())).thenReturn(policyMap); + when(traceabilityProperties.getRightOperand()).thenReturn("my-constraint4"); when(traceabilityProperties.getLeftOperand()).thenReturn("my-left-constraint4"); - // When + // When Optional policyResult = policyService.getFirstPolicyMatchingApplicationConstraint(); // Then assertThat(policyResult).isPresent(); assertThat(policyResult.get().policyId()).isEqualTo("test4"); - } @Test void getPolicyByConstraintRightOperandNotFound() { // Given - IrsPolicyResponse firstPolicyResponse = createIrsPolicyResponse("test", OffsetDateTime.now(), "my-constraint1", "","",""); - IrsPolicyResponse secondPolicyResponse = createIrsPolicyResponse("test2", OffsetDateTime.now(), "my-constraint2", "","",""); - IrsPolicyResponse thirdPolicyResponse = createIrsPolicyResponse("test3", OffsetDateTime.now(), "my-constraint3", "","",""); - IrsPolicyResponse fourthPolicyResponse = createIrsPolicyResponse("test4", OffsetDateTime.now(), "my-constraint4", "","",""); - List policyResponseList = List.of(firstPolicyResponse, secondPolicyResponse, thirdPolicyResponse, fourthPolicyResponse); - when(policyRepository.getPolicies()).thenReturn(policyResponseList); + IrsPolicyResponse firstPolicyResponse = createIrsPolicyResponse("test", OffsetDateTime.now(), "my-constraint1", "", "", ""); + IrsPolicyResponse secondPolicyResponse = createIrsPolicyResponse("test2", OffsetDateTime.now(), "my-constraint2", "", "", ""); + IrsPolicyResponse thirdPolicyResponse = createIrsPolicyResponse("test3", OffsetDateTime.now(), "my-constraint3", "", "", ""); + IrsPolicyResponse fourthPolicyResponse = createIrsPolicyResponse("test4", OffsetDateTime.now(), "my-constraint4", "", "", ""); + Map> policyResponseMap = Map.of( + "key1", List.of(firstPolicyResponse), + "key2", List.of(secondPolicyResponse), + "key3", List.of(thirdPolicyResponse), + "key4", List.of(fourthPolicyResponse) + ); + when(policyRepository.getPolicies()).thenReturn(policyResponseMap); when(traceabilityProperties.getRightOperand()).thenReturn("not-exists"); // When - Optional policyResult = policyService.getFirstPolicyMatchingApplicationConstraint(); // Then assertThat(policyResult).isEmpty(); - } - } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImplTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImplTest.java index 1b77177385..d94e1e7d58 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImplTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/JobRepositoryImplTest.java @@ -19,24 +19,14 @@ package org.eclipse.tractusx.traceability.assets.infrastructure.base.irs; -import org.eclipse.tractusx.irs.edc.client.policy.Constraint; -import org.eclipse.tractusx.irs.edc.client.policy.Constraints; -import org.eclipse.tractusx.irs.edc.client.policy.Operator; -import org.eclipse.tractusx.irs.edc.client.policy.OperatorType; -import org.eclipse.tractusx.irs.edc.client.policy.Permission; -import org.eclipse.tractusx.irs.edc.client.policy.Policy; -import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.BomLifecycle; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request.RegisterJobRequest; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Direction; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Payload; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.factory.IrsResponseAssetMapper; import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.relationship.Aspect; import org.eclipse.tractusx.traceability.bpn.infrastructure.repository.BpnRepository; import org.eclipse.tractusx.traceability.common.model.BPN; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -45,11 +35,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.OffsetDateTime; -import java.util.List; import java.util.stream.Stream; -import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -73,7 +60,7 @@ class JobRepositoryImplTest { private BpnRepository bpnRepository; @Mock - private IrsClient irsClient; + private JobClient jobClient; @Mock private IrsResponseAssetMapper assetMapperFactory; @@ -88,7 +75,7 @@ void testFindAssets_completedJob_returnsConvertedAssets(Direction direction) { jobRepositoryImpl.createJobToResolveAssets("1", direction, Aspect.downwardAspectsForAssetsAsBuilt(), BomLifecycle.AS_BUILT); // Then - verify(irsClient, times(1)).registerJob(any(RegisterJobRequest.class)); + verify(jobClient, times(1)).registerJob(any(RegisterJobRequest.class)); } private static Stream provideDirections() { @@ -98,29 +85,4 @@ private static Stream provideDirections() { ); } - - @Test - void test_getPolicyConstraints() { - //GIVEN - - OffsetDateTime validUntil = OffsetDateTime.now(); - OffsetDateTime createdOn = OffsetDateTime.now(); - List andConstraints = List.of(new Constraint("leftOperand", new Operator(OperatorType.EQ), "rightOperand")); - List orConstraints = List.of(new Constraint("leftOperand", new Operator(OperatorType.EQ), "rightOperand")); - Constraints constraints = new Constraints(andConstraints, orConstraints); - - Policy policy = new Policy("test", createdOn, validUntil, List.of(new Permission(PolicyType.USE, constraints))); - Payload payload = new Payload(null, "test", policy); - - final IrsPolicyResponse existingPolicy = new IrsPolicyResponse(validUntil, payload); - - - when(irsClient.getPolicies()).thenReturn(List.of(existingPolicy)); - - //WHEN - List irsPolicyResponse = irsClient.getPolicies(); - - //THEN - assertThat(irsPolicyResponse).hasSize(1); - } } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/PolicyRepositoryImplTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/PolicyRepositoryImplTest.java index 66dc92f7f7..d14bf76ff2 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/PolicyRepositoryImplTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/PolicyRepositoryImplTest.java @@ -9,8 +9,8 @@ * https://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * @@ -26,9 +26,11 @@ import org.eclipse.tractusx.irs.edc.client.policy.Permission; import org.eclipse.tractusx.irs.edc.client.policy.Policy; import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Payload; +import policies.response.IrsPolicyResponse; +import policies.request.Payload; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; +import org.eclipse.tractusx.traceability.policies.infrastructure.PolicyClient; +import org.eclipse.tractusx.traceability.policies.infrastructure.PolicyRepositoryImpl; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -38,6 +40,7 @@ import java.time.OffsetDateTime; import java.util.Collections; import java.util.List; +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.times; @@ -53,27 +56,25 @@ class PolicyRepositoryImplTest { @Mock TraceabilityProperties traceabilityProperties; - @Mock - private IrsClient irsClient; - + private PolicyClient policyClient; @Test - void givenNoPolicyExist_whenCreateIrsPolicyIfMissing_thenCreateIt() { + void givenNoPolicyExist_whenCreateIrsPolicyIfMissing_thenCreateApplicationConfigurationPolicyInIt() { // given - when(irsClient.getPolicies()).thenReturn(Collections.emptyList()); + when(policyClient.getPolicies()).thenReturn(Collections.emptyMap()); when(traceabilityProperties.getRightOperand()).thenReturn("test"); // when - policyRepositoryImpl.createIrsPolicyIfMissing(); + policyRepositoryImpl.createPolicyBasedOnAppConfig(); // then - verify(irsClient, times(1)) - .registerPolicy(); + verify(policyClient, times(1)) + .createPolicyFromAppConfig(); } @Test - void givenPolicyExist_whenCreateIrsPolicyIfMissing_thenDoNotCreateIt() { + void givenPolicyExist_whenCreateIrsPolicyIfMissing_thenDoNotCreateApplicationConfigurationPolicyInIt() { // given OffsetDateTime validUntil = OffsetDateTime.parse("2023-07-03T16:01:05.309Z"); OffsetDateTime createdOn = OffsetDateTime.now(); @@ -82,22 +83,22 @@ void givenPolicyExist_whenCreateIrsPolicyIfMissing_thenDoNotCreateIt() { Policy policy = new Policy("1", createdOn, validUntil, List.of(new Permission(PolicyType.USE, new Constraints(List.of(constraint, constraintSecond), List.of())))); Payload payload = new Payload(null, "1", policy); final IrsPolicyResponse existingPolicy = new IrsPolicyResponse(validUntil, payload); - when(irsClient.getPolicies()).thenReturn(List.of(existingPolicy)); + Map> existingPolicies = Map.of("key", List.of(existingPolicy)); + when(policyClient.getPolicies()).thenReturn(existingPolicies); when(traceabilityProperties.getRightOperand()).thenReturn("test"); when(traceabilityProperties.getRightOperandSecond()).thenReturn("test2"); when(traceabilityProperties.getValidUntil()).thenReturn(OffsetDateTime.parse("2023-07-02T16:01:05.309Z")); // when - policyRepositoryImpl.createIrsPolicyIfMissing(); + policyRepositoryImpl.createPolicyBasedOnAppConfig(); // then - verifyNoMoreInteractions(irsClient); + verifyNoMoreInteractions(policyClient); } @Test - void givenOutdatedPolicyExist_whenCreateIrsPolicyIfMissing_thenUpdateIt() { + void givenOutdatedPolicyExist_whenCreatePolicyBasedOnAppConfig_thenUpdateIt() { // given - OffsetDateTime validUntil = OffsetDateTime.parse("2023-07-03T16:01:05.309Z"); OffsetDateTime createdOn = OffsetDateTime.now(); Constraint constraint = new Constraint("leftOperand1", new Operator(OperatorType.EQ), "test"); @@ -106,25 +107,23 @@ void givenOutdatedPolicyExist_whenCreateIrsPolicyIfMissing_thenUpdateIt() { Payload payload = new Payload(null, "test", policy); final IrsPolicyResponse existingPolicy = new IrsPolicyResponse(validUntil, payload); - when(irsClient.getPolicies()).thenReturn(List.of(existingPolicy)); + Map> existingPolicies = Map.of("key", List.of(existingPolicy)); + when(policyClient.getPolicies()).thenReturn(existingPolicies); when(traceabilityProperties.getRightOperand()).thenReturn("test"); when(traceabilityProperties.getRightOperandSecond()).thenReturn("test2"); - when(traceabilityProperties.getValidUntil()).thenReturn(OffsetDateTime.parse("2023-07-04T16:01:05.309Z")); // when - policyRepositoryImpl.createIrsPolicyIfMissing(); + policyRepositoryImpl.createPolicyBasedOnAppConfig(); // then - verify(irsClient, times(1)).deletePolicy(); - verify(irsClient, times(1)).registerPolicy(); + verify(policyClient, times(1)).deletePolicy(traceabilityProperties.getRightOperand()); + verify(policyClient, times(1)).createPolicyFromAppConfig(); } - @Test void test_getPolicyConstraints() { - //GIVEN - + // GIVEN OffsetDateTime validUntil = OffsetDateTime.now(); OffsetDateTime createdOn = OffsetDateTime.now(); List andConstraints = List.of(new Constraint("leftOperand", new Operator(OperatorType.EQ), "rightOperand")); @@ -135,14 +134,14 @@ void test_getPolicyConstraints() { Payload payload = new Payload(null, "test", policy); final IrsPolicyResponse existingPolicy = new IrsPolicyResponse(validUntil, payload); + Map> existingPolicies = Map.of("key", List.of(existingPolicy)); + when(policyClient.getPolicies()).thenReturn(existingPolicies); - when(irsClient.getPolicies()).thenReturn(List.of(existingPolicy)); - - //WHEN - List irsPolicyResponse = irsClient.getPolicies(); + // WHEN + Map> irsPolicyResponseMap = policyClient.getPolicies(); - //THEN - assertThat(irsPolicyResponse).hasSize(1); + // THEN + assertThat(irsPolicyResponseMap.values().stream().flatMap(List::stream).toList()).hasSize(1); } } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfigTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfigTest.java index 0dbb28d7e3..3ffc1502c5 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfigTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/common/config/ApplicationStartupConfigTest.java @@ -20,7 +20,7 @@ package org.eclipse.tractusx.traceability.common.config; import org.eclipse.tractusx.traceability.assets.domain.base.JobRepository; -import org.eclipse.tractusx.traceability.assets.domain.base.PolicyRepository; +import org.eclipse.tractusx.traceability.policies.domain.PolicyRepository; import org.eclipse.tractusx.traceability.notification.domain.contract.EdcNotificationContractService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -57,7 +57,7 @@ void whenCallRegisterIrsPolicy_thenCallRepository() { applicationStartupConfig.registerIrsPolicy(); // then - verify(policyRepository, times(1)).createIrsPolicyIfMissing(); + verify(policyRepository, times(1)).createPolicyBasedOnAppConfig(); }); executor.shutdown(); diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/infrastructure/edc/notificationcontract/service/EdcNotificationContractServiceTest.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/infrastructure/edc/notificationcontract/service/EdcNotificationContractServiceTest.java index dba9c0aecb..a2369d3f49 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/infrastructure/edc/notificationcontract/service/EdcNotificationContractServiceTest.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/infrastructure/edc/notificationcontract/service/EdcNotificationContractServiceTest.java @@ -21,7 +21,7 @@ package org.eclipse.tractusx.traceability.infrastructure.edc.notificationcontract.service; -import assets.importpoc.PolicyResponse; +import policies.response.PolicyResponse; import org.eclipse.tractusx.irs.edc.client.asset.EdcAssetService; import org.eclipse.tractusx.irs.edc.client.asset.model.exception.CreateEdcAssetException; import org.eclipse.tractusx.irs.edc.client.asset.model.exception.DeleteEdcAssetException; @@ -31,8 +31,8 @@ import org.eclipse.tractusx.irs.edc.client.policy.model.exception.CreateEdcPolicyDefinitionException; import org.eclipse.tractusx.irs.edc.client.policy.model.exception.DeleteEdcPolicyDefinitionException; import org.eclipse.tractusx.irs.edc.client.policy.service.EdcPolicyDefinitionService; -import org.eclipse.tractusx.traceability.assets.application.importpoc.PolicyService; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; +import org.eclipse.tractusx.traceability.policies.application.service.PolicyService; +import policies.response.IrsPolicyResponse; import org.eclipse.tractusx.traceability.common.properties.TraceabilityProperties; import org.eclipse.tractusx.traceability.notification.application.contract.model.CreateNotificationContractException; import org.eclipse.tractusx.traceability.notification.application.contract.model.CreateNotificationContractRequest; @@ -48,6 +48,7 @@ import java.time.OffsetDateTime; import java.util.List; +import java.util.Map; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -88,9 +89,16 @@ void testHandle() throws CreateEdcAssetException, CreateEdcPolicyDefinitionExcep // given NotificationType notificationType = NotificationType.QUALITY_INVESTIGATION; NotificationMethod notificationMethod = NotificationMethod.RESOLVE; - List policyResponses = IrsPolicyResponse.toResponse(List.of(createIrsPolicyResponse("test", OffsetDateTime.now(), "orLeft", "andLeft", "or", "and"))); + + // Create a single IrsPolicyResponse and put it into a map + IrsPolicyResponse irsPolicyResponse = createIrsPolicyResponse("test", OffsetDateTime.now(), "orLeft", "andLeft", "or", "and"); + Map> policyMap = Map.of("testKey", List.of(irsPolicyResponse)); + + List policyResponses = IrsPolicyResponse.toResponse(policyMap); when(policyService.getFirstPolicyMatchingApplicationConstraint()).thenReturn(Optional.of(policyResponses.get(0))); + CreateNotificationContractRequest request = new CreateNotificationContractRequest(notificationType, notificationMethod); + when(edcNotificationAssetService.createNotificationAsset(any(), any(), any(), any())).thenReturn(notificationAssetId); when(traceabilityProperties.getUrl()).thenReturn("https://test"); when(edcPolicyDefinitionService.createAccessPolicy(any(EdcCreatePolicyDefinitionRequest.class))).thenReturn(accessPolicyId); @@ -110,6 +118,7 @@ void testHandle() throws CreateEdcAssetException, CreateEdcPolicyDefinitionExcep org.eclipse.tractusx.irs.edc.client.asset.model.NotificationType.QUALITY_INVESTIGATION); } + @Test void givenService_whenAssetCreationThrowsException_thenThrowException() throws CreateEdcAssetException { // given @@ -128,11 +137,19 @@ void givenService_whenPolicyDefinitionServiceThrowsException_thenThrowException( // given NotificationType notificationType = NotificationType.QUALITY_INVESTIGATION; NotificationMethod notificationMethod = NotificationMethod.RESOLVE; - List policyResponses = IrsPolicyResponse.toResponse(List.of(createIrsPolicyResponse("test", OffsetDateTime.now(), "orLeft", "andLeft", "or", "and"))); + + // Create a single IrsPolicyResponse and put it into a map + IrsPolicyResponse irsPolicyResponse = createIrsPolicyResponse("test", OffsetDateTime.now(), "orLeft", "andLeft", "or", "and"); + Map> policyMap = Map.of("testKey", List.of(irsPolicyResponse)); + + List policyResponses = IrsPolicyResponse.toResponse(policyMap); when(policyService.getFirstPolicyMatchingApplicationConstraint()).thenReturn(Optional.of(policyResponses.get(0))); + CreateNotificationContractRequest request = new CreateNotificationContractRequest(notificationType, notificationMethod); + when(edcNotificationAssetService.createNotificationAsset(any(), any(), any(), any())).thenReturn(notificationAssetId); when(traceabilityProperties.getUrl()).thenReturn("https://test"); + doThrow(CreateEdcPolicyDefinitionException.class).when(edcPolicyDefinitionService).createAccessPolicy(any(EdcCreatePolicyDefinitionRequest.class)); // when/then @@ -140,14 +157,22 @@ void givenService_whenPolicyDefinitionServiceThrowsException_thenThrowException( verify(edcNotificationAssetService).deleteAsset(any()); } + @Test void givenService_whenContractDefinitionServiceThrowsException_thenThrowException() throws CreateEdcAssetException, CreateEdcContractDefinitionException, DeleteEdcAssetException, DeleteEdcPolicyDefinitionException { // given NotificationType notificationType = NotificationType.QUALITY_INVESTIGATION; NotificationMethod notificationMethod = NotificationMethod.RESOLVE; - CreateNotificationContractRequest request = new CreateNotificationContractRequest(notificationType, notificationMethod); - List policyResponses = IrsPolicyResponse.toResponse(List.of(createIrsPolicyResponse("test", OffsetDateTime.now(),"orLeft", "andLeft", "or", "and"))); + + // Create a single IrsPolicyResponse and put it into a map + IrsPolicyResponse irsPolicyResponse = createIrsPolicyResponse("test", OffsetDateTime.now(), "orLeft", "andLeft", "or", "and"); + Map> policyMap = Map.of("testKey", List.of(irsPolicyResponse)); + + List policyResponses = IrsPolicyResponse.toResponse(policyMap); when(policyService.getFirstPolicyMatchingApplicationConstraint()).thenReturn(Optional.of(policyResponses.get(0))); + + CreateNotificationContractRequest request = new CreateNotificationContractRequest(notificationType, notificationMethod); + when(edcNotificationAssetService.createNotificationAsset(any(), any(), any(), any())).thenReturn(notificationAssetId); when(traceabilityProperties.getUrl()).thenReturn("https://test"); @@ -158,4 +183,5 @@ void givenService_whenContractDefinitionServiceThrowsException_thenThrowExceptio verify(edcPolicyDefinitionService).deleteAccessPolicy(any()); verify(edcNotificationAssetService).deleteAsset(any()); } + } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/common/support/IrsApiSupport.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/common/support/IrsApiSupport.java index d07db595c5..4eddd26b45 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/common/support/IrsApiSupport.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/common/support/IrsApiSupport.java @@ -18,11 +18,18 @@ ********************************************************************************/ package org.eclipse.tractusx.traceability.integration.common.support; +import com.xebialabs.restito.semantics.Action; +import com.xebialabs.restito.semantics.Condition; import org.glassfish.grizzly.http.util.HttpStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + import static com.xebialabs.restito.builder.stub.StubHttp.whenHttp; import static com.xebialabs.restito.semantics.Action.header; import static com.xebialabs.restito.semantics.Action.ok; @@ -95,14 +102,60 @@ public void irsJobDetailsApiFailed() { ); } + public void irsApiReturnsPolicies() { whenHttp(restitoProvider.stubServer()).match( - get("/irs/policies") - ) - .then( - status(HttpStatus.OK_200), - header("Content-Type", "application/json"), - restitoProvider.jsonResponseFromFile("./stubs/irs/policies/response_200_get_policies.json") - ); + Condition.get("/irs/policies") + ).then( + Action.status(HttpStatus.OK_200), + Action.header("Content-Type", "application/json"), + restitoProvider.jsonResponseFromFile("./stubs/irs/policies/response_200_get_policies.json") + ); + } + + public void irsApiReturnsPolicyById(String policyId) { + whenHttp(restitoProvider.stubServer()).match( + Condition.get("/irs/policies/" + policyId) + ).then( + Action.status(HttpStatus.OK_200), + Action.header("Content-Type", "application/json"), + restitoProvider.jsonResponseFromFile("./stubs/irs/policies/response_200_get_policyById.json") + ); + } + + public void irsApiCreatesPolicy() { + whenHttp(restitoProvider.stubServer()).match( + Condition.post("/irs/policies") + ).then( + Action.status(HttpStatus.OK_200), + Action.header("Content-Type", "application/json"), + restitoProvider.jsonResponseFromFile("./stubs/irs/policies/response_200_createPolicy.json") + ); + } + + public void irsApiUpdatesPolicy() { + whenHttp(restitoProvider.stubServer()).match( + Condition.put("/irs/policies") + ).then( + Action.status(HttpStatus.OK_200), + Action.header("Content-Type", "application/json"), + Action.stringContent("{\"message\": \"Policy updated successfully.\"}") + ); + } + + public void irsApiDeletesPolicy(String policyId) { + whenHttp(restitoProvider.stubServer()).match( + Condition.delete("/irs/policies/" + policyId) + ).then( + Action.status(HttpStatus.OK_200), + Action.header("Content-Type", "application/json"), + Action.stringContent("{\"message\": \"Policy deleted successfully.\"}") + ); + } + + private String readFile(String filePath) throws IOException { + // Implement reading file content from the specified filePath + // This is a utility method to read the JSON response from a file + return new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8); } } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/importdata/ImportControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/importdata/ImportControllerIT.java index 8f96f8f904..8cbfbab025 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/importdata/ImportControllerIT.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/importdata/ImportControllerIT.java @@ -46,6 +46,7 @@ import org.springframework.beans.factory.annotation.Autowired; import java.io.File; +import java.io.IOException; import java.util.List; import static io.restassured.RestAssured.given; @@ -375,7 +376,7 @@ void givenInvalidAspect_whenImportData_thenValidationShouldNotPass() throws Jose } @Test - void givenValidFile_whenPublishData_thenStatusShouldChangeToInPublishedToCX() throws JoseException, InterruptedException { + void givenValidFile_whenPublishData_thenStatusShouldChangeToInPublishedToCX() throws JoseException, InterruptedException, IOException { // given String path = getClass().getResource("/testdata/importfiles/validImportFile.json").getFile(); File file = new File(path); @@ -420,7 +421,7 @@ void givenValidFile_whenPublishData_thenStatusShouldChangeToInPublishedToCX() th } @Test - void givenValidFile2_whenPublishData_thenStatusShouldChangeToPublishedToCx() throws JoseException, InterruptedException { + void givenValidFile2_whenPublishData_thenStatusShouldChangeToPublishedToCx() throws JoseException, InterruptedException, IOException { // given String path = getClass().getResource("/testdata/importfiles/validImportFile.json").getFile(); File file = new File(path); @@ -469,7 +470,7 @@ void givenValidFile2_whenPublishData_thenStatusShouldChangeToPublishedToCx() thr } @Test - void givenValidFile_whenPublishDataFailsOnDtr_thenStatusShouldChangeError() throws JoseException, InterruptedException { + void givenValidFile_whenPublishDataFailsOnDtr_thenStatusShouldChangeError() throws JoseException, InterruptedException, IOException { // given String path = getClass().getResource("/testdata/importfiles/validImportFile.json").getFile(); File file = new File(path); @@ -515,7 +516,7 @@ void givenValidFile_whenPublishDataFailsOnDtr_thenStatusShouldChangeError() thro } @Test - void givenValidFile_whenPublishDataFailsOnPolicy_thenStatusShouldChangeError() throws JoseException, InterruptedException { + void givenValidFile_whenPublishDataFailsOnPolicy_thenStatusShouldChangeError() throws JoseException, InterruptedException, IOException { // given String path = getClass().getResource("/testdata/importfiles/validImportFile.json").getFile(); File file = new File(path); @@ -561,7 +562,7 @@ void givenValidFile_whenPublishDataFailsOnPolicy_thenStatusShouldChangeError() t } @Test - void givenValidFile_whenPublishDataFailsOnEdcPolicyCreation_thenStatusShouldChangeError() throws JoseException, InterruptedException { + void givenValidFile_whenPublishDataFailsOnEdcPolicyCreation_thenStatusShouldChangeError() throws JoseException, InterruptedException, IOException { // given String path = getClass().getResource("/testdata/importfiles/validImportFile.json").getFile(); File file = new File(path); @@ -607,7 +608,7 @@ void givenValidFile_whenPublishDataFailsOnEdcPolicyCreation_thenStatusShouldChan } @Test - void givenValidFile_whenPublishDataFailsOnEdcAssetCreation_thenStatusShouldChangeError() throws JoseException, InterruptedException { + void givenValidFile_whenPublishDataFailsOnEdcAssetCreation_thenStatusShouldChangeError() throws JoseException, InterruptedException, IOException { // given String path = getClass().getResource("/testdata/importfiles/validImportFile.json").getFile(); File file = new File(path); @@ -653,7 +654,7 @@ void givenValidFile_whenPublishDataFailsOnEdcAssetCreation_thenStatusShouldChang } @Test - void givenValidFile_whenPublishDataFailsOnEdcContractCreation_thenStatusShouldChangeError() throws JoseException, InterruptedException { + void givenValidFile_whenPublishDataFailsOnEdcContractCreation_thenStatusShouldChangeError() throws JoseException, InterruptedException, IOException { // given String path = getClass().getResource("/testdata/importfiles/validImportFile.json").getFile(); File file = new File(path); diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/policy/PolicyControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/policy/PolicyControllerIT.java index 3c824e5e50..22f6895b32 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/policy/PolicyControllerIT.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/policy/PolicyControllerIT.java @@ -24,10 +24,16 @@ import org.jose4j.lang.JoseException; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import policies.request.Payload; +import policies.request.RegisterPolicyRequest; +import policies.request.UpdatePolicyRequest; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.List; import static io.restassured.RestAssured.given; import static org.eclipse.tractusx.traceability.common.security.JwtRole.ADMIN; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; class PolicyControllerIT extends IntegrationTestSpecification { @@ -35,7 +41,7 @@ class PolicyControllerIT extends IntegrationTestSpecification { IrsApiSupport irsApiSupport; @Test - void shouldReturnPolicy() throws JoseException { + void shouldReturnGetPolicies() throws JoseException { //GIVEN irsApiSupport.irsApiReturnsPolicies(); @@ -51,4 +57,75 @@ void shouldReturnPolicy() throws JoseException { .log().all(); } + + @Test + void shouldReturnGetPolicyById() throws JoseException { + // GIVEN + String policyId = "default-policy"; + irsApiSupport.irsApiReturnsPolicies(); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .contentType(ContentType.JSON) + .when() + .get("/api/policies/" + policyId) + .then() + .statusCode(200) + .log().all(); + } + + @Test + void shouldCreatePolicy() throws JoseException { + // GIVEN + irsApiSupport.irsApiCreatesPolicy(); + Payload payload = Payload.builder().build(); + RegisterPolicyRequest request = new RegisterPolicyRequest(Instant.MAX, "abc", payload); + + // when/then + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .contentType(ContentType.JSON) + .body(request) + .when() + .post("/api/policies") + .then() + .statusCode(200) + .log().all(); + } + + @Test + void shouldUpdatePolicy() throws JoseException { + // GIVEN + irsApiSupport.irsApiUpdatesPolicy(); + + UpdatePolicyRequest request = new UpdatePolicyRequest(List.of("abc"), List.of("abc"), Instant.MAX); + + // when/then + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .contentType(ContentType.JSON) + .body(request) + .when() + .put("/api/policies") + .then() + .statusCode(200) + .log().all(); + } + + @Test + void shouldDeletePolicy() throws JoseException { + // GIVEN + String policyId = "policy1"; + irsApiSupport.irsApiDeletesPolicy(policyId); + + // when/then + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .contentType(ContentType.JSON) + .when() + .delete("/api/policies/" + policyId) + .then() + .statusCode(200) + .log().all(); + } } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/PolicyTestDataFactory.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/PolicyTestDataFactory.java index a945b19132..3cfbedb068 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/PolicyTestDataFactory.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/PolicyTestDataFactory.java @@ -25,8 +25,8 @@ import org.eclipse.tractusx.irs.edc.client.policy.Permission; import org.eclipse.tractusx.irs.edc.client.policy.Policy; import org.eclipse.tractusx.irs.edc.client.policy.PolicyType; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Payload; +import policies.response.IrsPolicyResponse; +import policies.request.Payload; import org.jetbrains.annotations.NotNull; import java.time.OffsetDateTime; diff --git a/tx-backend/src/test/resources/stubs/irs/policies/response_200_createPolicy.json b/tx-backend/src/test/resources/stubs/irs/policies/response_200_createPolicy.json new file mode 100644 index 0000000000..200affa790 --- /dev/null +++ b/tx-backend/src/test/resources/stubs/irs/policies/response_200_createPolicy.json @@ -0,0 +1,3 @@ +{ + "policyId": "default-policy3342" +} diff --git a/tx-backend/src/test/resources/stubs/irs/policies/response_200_get_policyById.json b/tx-backend/src/test/resources/stubs/irs/policies/response_200_get_policyById.json new file mode 100644 index 0000000000..f6ec5b1e85 --- /dev/null +++ b/tx-backend/src/test/resources/stubs/irs/policies/response_200_get_policyById.json @@ -0,0 +1,102 @@ +{ + "BPNL00000003CML1" : [ + { + "validUntil" : "2029-04-03T13:04:58.000819786Z", + "payload" : { + "@context" : { + "@vocab" : "https://w3id.org/edc/v0.0.1/ns/", + "edc" : "https://w3id.org/edc/v0.0.1/ns/", + "cx-policy" : "https://w3id.org/catenax/policy/", + "odrl" : "http://www.w3.org/ns/odrl/2/" + }, + "@id" : "policy1", + "policy" : { + "policyId" : "policy1", + "createdOn" : "2024-04-03T13:04:58.000772685Z", + "validUntil" : "2029-04-03T13:04:58.000819786Z", + "permissions" : [ + { + "action" : "use", + "constraint" : { + "and" : [ + { + "leftOperand" : "PURPOSE", + "operator" : { + "@id" : "eq" + }, + "odrl:rightOperand" : "ID 3.0 Trace" + }, + { + "leftOperand" : "PURPOSE", + "operator" : { + "@id" : "eq" + }, + "odrl:rightOperand" : "ID 3.1 Trace" + }, + { + "leftOperand" : "PURPOSE", + "operator" : { + "@id" : "eq" + }, + "odrl:rightOperand" : "R2_Traceability" + }, + { + "leftOperand" : "FrameworkAgreement.traceability", + "operator" : { + "@id" : "eq" + }, + "odrl:rightOperand" : "active" + }, + { + "leftOperand" : "Membership", + "operator" : { + "@id" : "eq" + }, + "odrl:rightOperand" : "active" + } + ], + "or" : [ + { + "leftOperand" : "PURPOSE", + "operator" : { + "@id" : "eq" + }, + "odrl:rightOperand" : "ID 3.0 Trace" + }, + { + "leftOperand" : "PURPOSE", + "operator" : { + "@id" : "eq" + }, + "odrl:rightOperand" : "ID 3.1 Trace" + }, + { + "leftOperand" : "PURPOSE", + "operator" : { + "@id" : "eq" + }, + "odrl:rightOperand" : "R2_Traceability" + }, + { + "leftOperand" : "FrameworkAgreement.traceability", + "operator" : { + "@id" : "eq" + }, + "odrl:rightOperand" : "active" + }, + { + "leftOperand" : "Membership", + "operator" : { + "@id" : "eq" + }, + "odrl:rightOperand" : "active" + } + ] + } + } + ] + } + } + } + ] +} diff --git a/tx-models/pom.xml b/tx-models/pom.xml index 2ca17f3c16..dbea30b569 100644 --- a/tx-models/pom.xml +++ b/tx-models/pom.xml @@ -44,6 +44,11 @@ SPDX-License-Identifier: Apache-2.0 swagger-annotations ${swagger-annotation.version} + + org.eclipse.tractusx.irs + irs-registry-client + ${irs-client-lib.version} + org.springframework.boot spring-boot-starter-validation diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/response/Context.java b/tx-models/src/main/java/policies/request/Context.java similarity index 86% rename from tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/response/Context.java rename to tx-models/src/main/java/policies/request/Context.java index 6965978865..d626f2cf16 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/response/Context.java +++ b/tx-models/src/main/java/policies/request/Context.java @@ -1,5 +1,6 @@ /******************************************************************************** - * Copyright (c) 2024 Contributors to the Eclipse Foundation + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -16,7 +17,7 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response; +package policies.request; /** * Context representation for get all policies response diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/response/Payload.java b/tx-models/src/main/java/policies/request/Payload.java similarity index 87% rename from tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/response/Payload.java rename to tx-models/src/main/java/policies/request/Payload.java index 4f6adb80b5..ab55f0b055 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/response/Payload.java +++ b/tx-models/src/main/java/policies/request/Payload.java @@ -1,5 +1,6 @@ /******************************************************************************** - * Copyright (c) 2024 Contributors to the Eclipse Foundation + * Copyright (c) 2022,2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -16,7 +17,7 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response; +package policies.request; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/request/RegisterPolicyRequest.java b/tx-models/src/main/java/policies/request/RegisterPolicyRequest.java similarity index 84% rename from tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/request/RegisterPolicyRequest.java rename to tx-models/src/main/java/policies/request/RegisterPolicyRequest.java index 998e39e1dd..47b7b141ec 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/request/RegisterPolicyRequest.java +++ b/tx-models/src/main/java/policies/request/RegisterPolicyRequest.java @@ -17,14 +17,13 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.request; - -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.Payload; +package policies.request; import java.time.Instant; public record RegisterPolicyRequest( Instant validUntil, + String businessPartnerNumber, Payload payload ) { diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/PolicyRepository.java b/tx-models/src/main/java/policies/request/UpdatePolicyRequest.java similarity index 76% rename from tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/PolicyRepository.java rename to tx-models/src/main/java/policies/request/UpdatePolicyRequest.java index fb9ce4cfe9..6979b8a71d 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/domain/base/PolicyRepository.java +++ b/tx-models/src/main/java/policies/request/UpdatePolicyRequest.java @@ -16,14 +16,14 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.traceability.assets.domain.base; - -import org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response.IrsPolicyResponse; +package policies.request; +import java.time.Instant; import java.util.List; -public interface PolicyRepository { - List getPolicies(); +public record UpdatePolicyRequest( + List businessPartnerNumbers, + List policyIds, + Instant validUntil +) {} - void createIrsPolicyIfMissing(); -} diff --git a/tx-models/src/main/java/assets/importpoc/ConstraintResponse.java b/tx-models/src/main/java/policies/response/ConstraintResponse.java similarity index 97% rename from tx-models/src/main/java/assets/importpoc/ConstraintResponse.java rename to tx-models/src/main/java/policies/response/ConstraintResponse.java index d4979f4905..6abb2a361e 100644 --- a/tx-models/src/main/java/assets/importpoc/ConstraintResponse.java +++ b/tx-models/src/main/java/policies/response/ConstraintResponse.java @@ -16,7 +16,7 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package assets.importpoc; +package policies.response; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; diff --git a/tx-models/src/main/java/assets/importpoc/ConstraintsResponse.java b/tx-models/src/main/java/policies/response/ConstraintsResponse.java similarity index 97% rename from tx-models/src/main/java/assets/importpoc/ConstraintsResponse.java rename to tx-models/src/main/java/policies/response/ConstraintsResponse.java index 8e9947f939..1c1b685ace 100644 --- a/tx-models/src/main/java/assets/importpoc/ConstraintsResponse.java +++ b/tx-models/src/main/java/policies/response/ConstraintsResponse.java @@ -16,7 +16,7 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package assets.importpoc; +package policies.response; import lombok.Builder; diff --git a/tx-models/src/main/java/policies/response/CreatePolicyResponse.java b/tx-models/src/main/java/policies/response/CreatePolicyResponse.java new file mode 100644 index 0000000000..c4abb90b67 --- /dev/null +++ b/tx-models/src/main/java/policies/response/CreatePolicyResponse.java @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +package policies.response; + +public record CreatePolicyResponse() { +} diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/response/IrsPolicyResponse.java b/tx-models/src/main/java/policies/response/IrsPolicyResponse.java similarity index 88% rename from tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/response/IrsPolicyResponse.java rename to tx-models/src/main/java/policies/response/IrsPolicyResponse.java index 878ecbc21a..f8d2837ae6 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/base/irs/model/response/IrsPolicyResponse.java +++ b/tx-models/src/main/java/policies/response/IrsPolicyResponse.java @@ -16,23 +16,21 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.traceability.assets.infrastructure.base.irs.model.response; +package policies.response; -import assets.importpoc.ConstraintResponse; -import assets.importpoc.ConstraintsResponse; -import assets.importpoc.OperatorTypeResponse; -import assets.importpoc.PermissionResponse; -import assets.importpoc.PolicyResponse; -import assets.importpoc.PolicyTypeResponse; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Builder; -import org.apache.commons.lang3.StringUtils; +import com.fasterxml.jackson.annotation.JsonFormat; import org.eclipse.tractusx.irs.edc.client.policy.Constraint; import org.eclipse.tractusx.irs.edc.client.policy.Permission; import org.eclipse.tractusx.irs.edc.client.policy.Policy; +import policies.request.Payload; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import org.apache.commons.lang3.StringUtils; + import java.time.OffsetDateTime; import java.util.List; +import java.util.Map; import static org.apache.commons.collections4.ListUtils.emptyIfNull; @@ -41,7 +39,7 @@ */ @Builder @Schema(example = IrsPolicyResponse.EXAMPLE_PAYLOAD) -public record IrsPolicyResponse(OffsetDateTime validUntil, Payload payload) { +public record IrsPolicyResponse(@JsonFormat(shape = JsonFormat.Shape.STRING) OffsetDateTime validUntil, Payload payload) { // https://github.com/eclipse-tractusx/traceability-foss/issues/978 // "odrl:action" USE will be use (lowercase) make sure to migrate @@ -89,16 +87,20 @@ public record IrsPolicyResponse(OffsetDateTime validUntil, Payload payload) { ] """; - public static List toResponse(List allPolicies) { - return allPolicies.stream().map(IrsPolicyResponse::toResponse).toList(); + public static List toResponse(Map> allPolicies) { + return allPolicies.entrySet().stream() + .flatMap(entry -> entry.getValue().stream() + .map(policy -> toResponse(policy, entry.getKey()))) + .toList(); } - public static PolicyResponse toResponse(IrsPolicyResponse policy) { + public static PolicyResponse toResponse(IrsPolicyResponse policy, String bpn) { return PolicyResponse.builder() .policyId(policy.payload().policyId()) .validUntil(policy.payload().policy().getValidUntil()) .createdOn(policy.payload().policy().getCreatedOn()) .permissions(map(policy.payload().policy())) + .businessPartnerNumber(bpn) .build(); } diff --git a/tx-models/src/main/java/assets/importpoc/OperatorTypeResponse.java b/tx-models/src/main/java/policies/response/OperatorTypeResponse.java similarity index 98% rename from tx-models/src/main/java/assets/importpoc/OperatorTypeResponse.java rename to tx-models/src/main/java/policies/response/OperatorTypeResponse.java index eb360f9ac5..316a075248 100644 --- a/tx-models/src/main/java/assets/importpoc/OperatorTypeResponse.java +++ b/tx-models/src/main/java/policies/response/OperatorTypeResponse.java @@ -16,7 +16,7 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package assets.importpoc; +package policies.response; import com.fasterxml.jackson.databind.annotation.JsonSerialize; diff --git a/tx-models/src/main/java/assets/importpoc/PermissionResponse.java b/tx-models/src/main/java/policies/response/PermissionResponse.java similarity index 97% rename from tx-models/src/main/java/assets/importpoc/PermissionResponse.java rename to tx-models/src/main/java/policies/response/PermissionResponse.java index 855fe4e6ff..3555053b9c 100644 --- a/tx-models/src/main/java/assets/importpoc/PermissionResponse.java +++ b/tx-models/src/main/java/policies/response/PermissionResponse.java @@ -16,7 +16,7 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package assets.importpoc; +package policies.response; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/tx-models/src/main/java/assets/importpoc/PolicyResponse.java b/tx-models/src/main/java/policies/response/PolicyResponse.java similarity index 92% rename from tx-models/src/main/java/assets/importpoc/PolicyResponse.java rename to tx-models/src/main/java/policies/response/PolicyResponse.java index c41c615d76..88e98eb0ce 100644 --- a/tx-models/src/main/java/assets/importpoc/PolicyResponse.java +++ b/tx-models/src/main/java/policies/response/PolicyResponse.java @@ -16,7 +16,7 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package assets.importpoc; +package policies.response; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -36,5 +36,6 @@ public record PolicyResponse( OffsetDateTime createdOn, @JsonSerialize(using = CustomOffsetDateTimeSerializer.class) OffsetDateTime validUntil, - List permissions) { + List permissions, + String businessPartnerNumber) { } diff --git a/tx-models/src/main/java/assets/importpoc/PolicyTypeResponse.java b/tx-models/src/main/java/policies/response/PolicyTypeResponse.java similarity index 97% rename from tx-models/src/main/java/assets/importpoc/PolicyTypeResponse.java rename to tx-models/src/main/java/policies/response/PolicyTypeResponse.java index e58d67c531..91ea12c0e6 100644 --- a/tx-models/src/main/java/assets/importpoc/PolicyTypeResponse.java +++ b/tx-models/src/main/java/policies/response/PolicyTypeResponse.java @@ -16,7 +16,7 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package assets.importpoc; +package policies.response; public enum PolicyTypeResponse { ACCESS,