From 63a50518da6e2524e3cde42814972a373a00c267 Mon Sep 17 00:00:00 2001 From: Buse Halis Date: Thu, 18 Jun 2026 15:20:28 +0200 Subject: [PATCH 1/7] refactor: rename sample-app to integration-tests - Rename sample-app/ directory to integration-tests/ to better reflect its purpose - Update root pom.xml module reference - Update coverage-report/pom.xml artifact references and JaCoCo exec paths - Update integration-tests pom artifactIds - Update .github/actions/test/action.yml working-directory - Update .github/actions/scan-with-sonar/action.yml exclusion path --- .github/actions/scan-with-sonar/action.yml | 2 +- .github/actions/test/action.yml | 2 +- coverage-report/pom.xml | 4 ++-- {sample-app => integration-tests}/.cdsrc.json | 0 {sample-app => integration-tests}/.gitignore | 0 {sample-app => integration-tests}/db/data-model.cds | 0 .../db/data/my.bookshop-Books.csv | 0 .../db/data/my.bookshop-Books_texts.csv | 0 {sample-app => integration-tests}/mta.yaml | 0 {sample-app => integration-tests}/package-lock.json | 0 {sample-app => integration-tests}/package.json | 0 {sample-app => integration-tests}/pom.xml | 4 ++-- {sample-app => integration-tests}/srv/_i18n/i18n.properties | 0 .../srv/_i18n/i18n_de.properties | 0 .../srv/_i18n/i18n_en.properties | 0 .../srv/_i18n/i18n_es.properties | 0 .../srv/_i18n/i18n_tr.properties | 0 {sample-app => integration-tests}/srv/cat-service.cds | 0 .../srv/notificationtypes-data.cds | 0 {sample-app => integration-tests}/srv/pom.xml | 6 +++--- .../srv/src/main/java/customer/sample_app/Application.java | 0 .../sample_app/config/DestinationConfiguration.java | 0 .../customer/sample_app/handlers/CatalogServiceHandler.java | 0 .../srv/src/main/resources/application.yaml | 0 .../resources/email-templates/certificate-expiration.html | 0 .../mock/NotificationProviderServiceMockHandler.java | 0 .../NotificationTemplateProviderServiceMockHandler.java | 0 .../mock/NotificationTypeProviderServiceMockHandler.java | 0 .../integration/EntityNotificationIntegrationTest.java | 0 .../sample_app/integration/LocalModeIntegrationTest.java | 0 .../sample_app/integration/NotificationIntegrationTest.java | 0 .../integration/NotificationTemplateProvisioningTest.java | 0 .../integration/NotificationTypeProvisioningTest.java | 0 .../sample_app/testdata/CertificateExpirationTestData.java | 0 .../sample_app/testdata/ContractDeadlineTestData.java | 0 .../sample_app/testdata/DeploymentNotificationTestData.java | 0 .../customer/sample_app/testdata/SecurityAlertTestData.java | 0 .../sample_app/testdata/ServerIncidentTestData.java | 0 .../sample_app/testdata/SystemMaintenanceTestData.java | 0 .../srv/src/test/resources/application-local.yaml | 0 .../srv/src/test/resources/application-test.yaml | 0 pom.xml | 2 +- 42 files changed, 10 insertions(+), 10 deletions(-) rename {sample-app => integration-tests}/.cdsrc.json (100%) rename {sample-app => integration-tests}/.gitignore (100%) rename {sample-app => integration-tests}/db/data-model.cds (100%) rename {sample-app => integration-tests}/db/data/my.bookshop-Books.csv (100%) rename {sample-app => integration-tests}/db/data/my.bookshop-Books_texts.csv (100%) rename {sample-app => integration-tests}/mta.yaml (100%) rename {sample-app => integration-tests}/package-lock.json (100%) rename {sample-app => integration-tests}/package.json (100%) rename {sample-app => integration-tests}/pom.xml (97%) rename {sample-app => integration-tests}/srv/_i18n/i18n.properties (100%) rename {sample-app => integration-tests}/srv/_i18n/i18n_de.properties (100%) rename {sample-app => integration-tests}/srv/_i18n/i18n_en.properties (100%) rename {sample-app => integration-tests}/srv/_i18n/i18n_es.properties (100%) rename {sample-app => integration-tests}/srv/_i18n/i18n_tr.properties (100%) rename {sample-app => integration-tests}/srv/cat-service.cds (100%) rename {sample-app => integration-tests}/srv/notificationtypes-data.cds (100%) rename {sample-app => integration-tests}/srv/pom.xml (97%) rename {sample-app => integration-tests}/srv/src/main/java/customer/sample_app/Application.java (100%) rename {sample-app => integration-tests}/srv/src/main/java/customer/sample_app/config/DestinationConfiguration.java (100%) rename {sample-app => integration-tests}/srv/src/main/java/customer/sample_app/handlers/CatalogServiceHandler.java (100%) rename {sample-app => integration-tests}/srv/src/main/resources/application.yaml (100%) rename {sample-app => integration-tests}/srv/src/main/resources/email-templates/certificate-expiration.html (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/handlers/mock/NotificationProviderServiceMockHandler.java (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/handlers/mock/NotificationTemplateProviderServiceMockHandler.java (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/handlers/mock/NotificationTypeProviderServiceMockHandler.java (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/integration/EntityNotificationIntegrationTest.java (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/integration/LocalModeIntegrationTest.java (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/integration/NotificationIntegrationTest.java (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/integration/NotificationTemplateProvisioningTest.java (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/integration/NotificationTypeProvisioningTest.java (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/testdata/CertificateExpirationTestData.java (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/testdata/ContractDeadlineTestData.java (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/testdata/DeploymentNotificationTestData.java (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/testdata/SecurityAlertTestData.java (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/testdata/ServerIncidentTestData.java (100%) rename {sample-app => integration-tests}/srv/src/test/java/customer/sample_app/testdata/SystemMaintenanceTestData.java (100%) rename {sample-app => integration-tests}/srv/src/test/resources/application-local.yaml (100%) rename {sample-app => integration-tests}/srv/src/test/resources/application-test.yaml (100%) diff --git a/.github/actions/scan-with-sonar/action.yml b/.github/actions/scan-with-sonar/action.yml index 4bcba15..658b86b 100644 --- a/.github/actions/scan-with-sonar/action.yml +++ b/.github/actions/scan-with-sonar/action.yml @@ -68,7 +68,7 @@ runs: -Dsonar.projectVersion=${{ steps.get-revision.outputs.REVISION }} -Dsonar.qualitygate.wait=true -Dsonar.java.source=17 - -Dsonar.exclusions=**/sample-app/** + -Dsonar.exclusions=**/integration-tests/** -Dsonar.coverage.jacoco.xmlReportPaths=${{ github.workspace }}/coverage-report/target/site/jacoco-aggregate/jacoco.xml -Dsonar.coverage.exclusions=**/src/test/**,**/src/gen/** -B -ntp diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml index bf8c211..09bb7cb 100644 --- a/.github/actions/test/action.yml +++ b/.github/actions/test/action.yml @@ -33,6 +33,6 @@ runs: - name: Test Sample App (Integration Tests) shell: bash - working-directory: sample-app/srv + working-directory: integration-tests/srv run: | mvn test -B -V \ No newline at end of file diff --git a/coverage-report/pom.xml b/coverage-report/pom.xml index 28ae685..65186b8 100644 --- a/coverage-report/pom.xml +++ b/coverage-report/pom.xml @@ -25,7 +25,7 @@ customer - sample-app + integration-tests ${revision} test @@ -93,7 +93,7 @@ ${project.basedir}/.. cds-feature-notifications/target/jacoco.exec - sample-app/srv/target/jacoco.exec + integration-tests/srv/target/jacoco.exec diff --git a/sample-app/.cdsrc.json b/integration-tests/.cdsrc.json similarity index 100% rename from sample-app/.cdsrc.json rename to integration-tests/.cdsrc.json diff --git a/sample-app/.gitignore b/integration-tests/.gitignore similarity index 100% rename from sample-app/.gitignore rename to integration-tests/.gitignore diff --git a/sample-app/db/data-model.cds b/integration-tests/db/data-model.cds similarity index 100% rename from sample-app/db/data-model.cds rename to integration-tests/db/data-model.cds diff --git a/sample-app/db/data/my.bookshop-Books.csv b/integration-tests/db/data/my.bookshop-Books.csv similarity index 100% rename from sample-app/db/data/my.bookshop-Books.csv rename to integration-tests/db/data/my.bookshop-Books.csv diff --git a/sample-app/db/data/my.bookshop-Books_texts.csv b/integration-tests/db/data/my.bookshop-Books_texts.csv similarity index 100% rename from sample-app/db/data/my.bookshop-Books_texts.csv rename to integration-tests/db/data/my.bookshop-Books_texts.csv diff --git a/sample-app/mta.yaml b/integration-tests/mta.yaml similarity index 100% rename from sample-app/mta.yaml rename to integration-tests/mta.yaml diff --git a/sample-app/package-lock.json b/integration-tests/package-lock.json similarity index 100% rename from sample-app/package-lock.json rename to integration-tests/package-lock.json diff --git a/sample-app/package.json b/integration-tests/package.json similarity index 100% rename from sample-app/package.json rename to integration-tests/package.json diff --git a/sample-app/pom.xml b/integration-tests/pom.xml similarity index 97% rename from sample-app/pom.xml rename to integration-tests/pom.xml index e84f303..38bfb81 100644 --- a/sample-app/pom.xml +++ b/integration-tests/pom.xml @@ -9,11 +9,11 @@ customer - sample-app-parent + integration-tests-parent ${revision} pom - sample-app parent + integration-tests parent srv diff --git a/sample-app/srv/_i18n/i18n.properties b/integration-tests/srv/_i18n/i18n.properties similarity index 100% rename from sample-app/srv/_i18n/i18n.properties rename to integration-tests/srv/_i18n/i18n.properties diff --git a/sample-app/srv/_i18n/i18n_de.properties b/integration-tests/srv/_i18n/i18n_de.properties similarity index 100% rename from sample-app/srv/_i18n/i18n_de.properties rename to integration-tests/srv/_i18n/i18n_de.properties diff --git a/sample-app/srv/_i18n/i18n_en.properties b/integration-tests/srv/_i18n/i18n_en.properties similarity index 100% rename from sample-app/srv/_i18n/i18n_en.properties rename to integration-tests/srv/_i18n/i18n_en.properties diff --git a/sample-app/srv/_i18n/i18n_es.properties b/integration-tests/srv/_i18n/i18n_es.properties similarity index 100% rename from sample-app/srv/_i18n/i18n_es.properties rename to integration-tests/srv/_i18n/i18n_es.properties diff --git a/sample-app/srv/_i18n/i18n_tr.properties b/integration-tests/srv/_i18n/i18n_tr.properties similarity index 100% rename from sample-app/srv/_i18n/i18n_tr.properties rename to integration-tests/srv/_i18n/i18n_tr.properties diff --git a/sample-app/srv/cat-service.cds b/integration-tests/srv/cat-service.cds similarity index 100% rename from sample-app/srv/cat-service.cds rename to integration-tests/srv/cat-service.cds diff --git a/sample-app/srv/notificationtypes-data.cds b/integration-tests/srv/notificationtypes-data.cds similarity index 100% rename from sample-app/srv/notificationtypes-data.cds rename to integration-tests/srv/notificationtypes-data.cds diff --git a/sample-app/srv/pom.xml b/integration-tests/srv/pom.xml similarity index 97% rename from sample-app/srv/pom.xml rename to integration-tests/srv/pom.xml index 24e3ef1..faf0bfa 100644 --- a/sample-app/srv/pom.xml +++ b/integration-tests/srv/pom.xml @@ -4,14 +4,14 @@ customer - sample-app-parent + integration-tests-parent ${revision} - sample-app + integration-tests jar - sample-app + integration-tests diff --git a/sample-app/srv/src/main/java/customer/sample_app/Application.java b/integration-tests/srv/src/main/java/customer/sample_app/Application.java similarity index 100% rename from sample-app/srv/src/main/java/customer/sample_app/Application.java rename to integration-tests/srv/src/main/java/customer/sample_app/Application.java diff --git a/sample-app/srv/src/main/java/customer/sample_app/config/DestinationConfiguration.java b/integration-tests/srv/src/main/java/customer/sample_app/config/DestinationConfiguration.java similarity index 100% rename from sample-app/srv/src/main/java/customer/sample_app/config/DestinationConfiguration.java rename to integration-tests/srv/src/main/java/customer/sample_app/config/DestinationConfiguration.java diff --git a/sample-app/srv/src/main/java/customer/sample_app/handlers/CatalogServiceHandler.java b/integration-tests/srv/src/main/java/customer/sample_app/handlers/CatalogServiceHandler.java similarity index 100% rename from sample-app/srv/src/main/java/customer/sample_app/handlers/CatalogServiceHandler.java rename to integration-tests/srv/src/main/java/customer/sample_app/handlers/CatalogServiceHandler.java diff --git a/sample-app/srv/src/main/resources/application.yaml b/integration-tests/srv/src/main/resources/application.yaml similarity index 100% rename from sample-app/srv/src/main/resources/application.yaml rename to integration-tests/srv/src/main/resources/application.yaml diff --git a/sample-app/srv/src/main/resources/email-templates/certificate-expiration.html b/integration-tests/srv/src/main/resources/email-templates/certificate-expiration.html similarity index 100% rename from sample-app/srv/src/main/resources/email-templates/certificate-expiration.html rename to integration-tests/srv/src/main/resources/email-templates/certificate-expiration.html diff --git a/sample-app/srv/src/test/java/customer/sample_app/handlers/mock/NotificationProviderServiceMockHandler.java b/integration-tests/srv/src/test/java/customer/sample_app/handlers/mock/NotificationProviderServiceMockHandler.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/handlers/mock/NotificationProviderServiceMockHandler.java rename to integration-tests/srv/src/test/java/customer/sample_app/handlers/mock/NotificationProviderServiceMockHandler.java diff --git a/sample-app/srv/src/test/java/customer/sample_app/handlers/mock/NotificationTemplateProviderServiceMockHandler.java b/integration-tests/srv/src/test/java/customer/sample_app/handlers/mock/NotificationTemplateProviderServiceMockHandler.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/handlers/mock/NotificationTemplateProviderServiceMockHandler.java rename to integration-tests/srv/src/test/java/customer/sample_app/handlers/mock/NotificationTemplateProviderServiceMockHandler.java diff --git a/sample-app/srv/src/test/java/customer/sample_app/handlers/mock/NotificationTypeProviderServiceMockHandler.java b/integration-tests/srv/src/test/java/customer/sample_app/handlers/mock/NotificationTypeProviderServiceMockHandler.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/handlers/mock/NotificationTypeProviderServiceMockHandler.java rename to integration-tests/srv/src/test/java/customer/sample_app/handlers/mock/NotificationTypeProviderServiceMockHandler.java diff --git a/sample-app/srv/src/test/java/customer/sample_app/integration/EntityNotificationIntegrationTest.java b/integration-tests/srv/src/test/java/customer/sample_app/integration/EntityNotificationIntegrationTest.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/integration/EntityNotificationIntegrationTest.java rename to integration-tests/srv/src/test/java/customer/sample_app/integration/EntityNotificationIntegrationTest.java diff --git a/sample-app/srv/src/test/java/customer/sample_app/integration/LocalModeIntegrationTest.java b/integration-tests/srv/src/test/java/customer/sample_app/integration/LocalModeIntegrationTest.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/integration/LocalModeIntegrationTest.java rename to integration-tests/srv/src/test/java/customer/sample_app/integration/LocalModeIntegrationTest.java diff --git a/sample-app/srv/src/test/java/customer/sample_app/integration/NotificationIntegrationTest.java b/integration-tests/srv/src/test/java/customer/sample_app/integration/NotificationIntegrationTest.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/integration/NotificationIntegrationTest.java rename to integration-tests/srv/src/test/java/customer/sample_app/integration/NotificationIntegrationTest.java diff --git a/sample-app/srv/src/test/java/customer/sample_app/integration/NotificationTemplateProvisioningTest.java b/integration-tests/srv/src/test/java/customer/sample_app/integration/NotificationTemplateProvisioningTest.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/integration/NotificationTemplateProvisioningTest.java rename to integration-tests/srv/src/test/java/customer/sample_app/integration/NotificationTemplateProvisioningTest.java diff --git a/sample-app/srv/src/test/java/customer/sample_app/integration/NotificationTypeProvisioningTest.java b/integration-tests/srv/src/test/java/customer/sample_app/integration/NotificationTypeProvisioningTest.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/integration/NotificationTypeProvisioningTest.java rename to integration-tests/srv/src/test/java/customer/sample_app/integration/NotificationTypeProvisioningTest.java diff --git a/sample-app/srv/src/test/java/customer/sample_app/testdata/CertificateExpirationTestData.java b/integration-tests/srv/src/test/java/customer/sample_app/testdata/CertificateExpirationTestData.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/testdata/CertificateExpirationTestData.java rename to integration-tests/srv/src/test/java/customer/sample_app/testdata/CertificateExpirationTestData.java diff --git a/sample-app/srv/src/test/java/customer/sample_app/testdata/ContractDeadlineTestData.java b/integration-tests/srv/src/test/java/customer/sample_app/testdata/ContractDeadlineTestData.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/testdata/ContractDeadlineTestData.java rename to integration-tests/srv/src/test/java/customer/sample_app/testdata/ContractDeadlineTestData.java diff --git a/sample-app/srv/src/test/java/customer/sample_app/testdata/DeploymentNotificationTestData.java b/integration-tests/srv/src/test/java/customer/sample_app/testdata/DeploymentNotificationTestData.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/testdata/DeploymentNotificationTestData.java rename to integration-tests/srv/src/test/java/customer/sample_app/testdata/DeploymentNotificationTestData.java diff --git a/sample-app/srv/src/test/java/customer/sample_app/testdata/SecurityAlertTestData.java b/integration-tests/srv/src/test/java/customer/sample_app/testdata/SecurityAlertTestData.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/testdata/SecurityAlertTestData.java rename to integration-tests/srv/src/test/java/customer/sample_app/testdata/SecurityAlertTestData.java diff --git a/sample-app/srv/src/test/java/customer/sample_app/testdata/ServerIncidentTestData.java b/integration-tests/srv/src/test/java/customer/sample_app/testdata/ServerIncidentTestData.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/testdata/ServerIncidentTestData.java rename to integration-tests/srv/src/test/java/customer/sample_app/testdata/ServerIncidentTestData.java diff --git a/sample-app/srv/src/test/java/customer/sample_app/testdata/SystemMaintenanceTestData.java b/integration-tests/srv/src/test/java/customer/sample_app/testdata/SystemMaintenanceTestData.java similarity index 100% rename from sample-app/srv/src/test/java/customer/sample_app/testdata/SystemMaintenanceTestData.java rename to integration-tests/srv/src/test/java/customer/sample_app/testdata/SystemMaintenanceTestData.java diff --git a/sample-app/srv/src/test/resources/application-local.yaml b/integration-tests/srv/src/test/resources/application-local.yaml similarity index 100% rename from sample-app/srv/src/test/resources/application-local.yaml rename to integration-tests/srv/src/test/resources/application-local.yaml diff --git a/sample-app/srv/src/test/resources/application-test.yaml b/integration-tests/srv/src/test/resources/application-test.yaml similarity index 100% rename from sample-app/srv/src/test/resources/application-test.yaml rename to integration-tests/srv/src/test/resources/application-test.yaml diff --git a/pom.xml b/pom.xml index 8539357..49d1d62 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ cds-feature-notifications - sample-app + integration-tests coverage-report From 7a2f38c112b2d8e97deb5b0389d2ad9d3641fcab Mon Sep 17 00:00:00 2001 From: Buse Halis Date: Tue, 30 Jun 2026 19:37:59 +0200 Subject: [PATCH 2/7] chore: replace personal email with placeholder, update application.yaml, untrack .DS_Store --- .DS_Store | Bin 6148 -> 0 bytes .../handlers/CatalogServiceHandler.java | 2 +- .../srv/src/main/resources/application.yaml | 13 ++++--------- 3 files changed, 5 insertions(+), 10 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 6e38920893a5860c4fe95713ca767f010a4944d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK+fKqj5Iu_|B9e%2J~qDF#6PGAK@ycjp?k-wXK-%sK0nHAY;U zw0zY(uOfR|_8#}~j9X0b$T%bWh-*#k^_rDd7KIrag&EfHthyf=b+^e&xYiJFjB$RQ z5H+*eJA4KmKEpOHh%5BB3T7E~%NSbqLMkt{3RR4#nC0+NZDSPRnJre^b*NDlPz6+h z9|dH825$JC+SIvC`*l<}&d7~ArxAQ*UzJf;pgLNks^bX4PiF^r?LKk#{x$JC*t z!`RB4$5%G~55?H(>;M1rB)w7vRDq3Bz*O6Vc8j;< z&(_k-$ypoFZ|Gu@mpZf*27eseh8)EQbZZ<7q(O{4rViOd(+>eFg9cULPZjtEewvwN diff --git a/integration-tests/srv/src/main/java/customer/sample_app/handlers/CatalogServiceHandler.java b/integration-tests/srv/src/main/java/customer/sample_app/handlers/CatalogServiceHandler.java index 7f9d3b4..36dd56a 100644 --- a/integration-tests/srv/src/main/java/customer/sample_app/handlers/CatalogServiceHandler.java +++ b/integration-tests/srv/src/main/java/customer/sample_app/handlers/CatalogServiceHandler.java @@ -45,7 +45,7 @@ public void discountBooks(Stream books) { // Create CertificateExpiration event CertificateExpiration certificateExpiration = CertificateExpiration.create(); - certificateExpiration.setRecipients("buse.halis@sap.com"); + certificateExpiration.setRecipients("admin@example.com"); certificateExpiration.setName("user"); certificateExpiration.setCertificateName("Cert"); certificateExpiration.setExpirationDate(LocalDate.of(2026, 3, 15)); diff --git a/integration-tests/srv/src/main/resources/application.yaml b/integration-tests/srv/src/main/resources/application.yaml index bbe555e..62ba5cb 100644 --- a/integration-tests/srv/src/main/resources/application.yaml +++ b/integration-tests/srv/src/main/resources/application.yaml @@ -33,6 +33,10 @@ cds: password: myPass roles: - cds.Developer + index-page.enabled: true + environment: + production: + enabled: true --- spring: config.activate.on-profile: test @@ -46,15 +50,6 @@ cds: - cds.Developer data-source.auto-config.enabled: false index-page.enabled: true - #remote: - #services: - #NotificationProviderService: - #destination.name: SAP_Notifications - #type: odata-v2 - #http: - #suffix: /v2 - #service: Notification.svc - #csrf.enabled: true environment: production: enabled: true From 1ccebbc315869c66c325f83a2aede0a6e3c6c2b0 Mon Sep 17 00:00:00 2001 From: Buse Halis Date: Tue, 30 Jun 2026 19:56:26 +0200 Subject: [PATCH 3/7] feat: add sample-app with BookOrdered, LowStockAlert, and StockReplenished notification examples --- sample-app/.cdsrc.json | 2 + sample-app/.gitignore | 31 + .../META-INF/cds4j-codegen/services.generated | 3 + sample-app/README.md | 339 +++ sample-app/app/_i18n/i18n.properties | 15 + sample-app/app/_i18n/i18n_de.properties | 15 + sample-app/app/admin-books/fiori-service.cds | 126 ++ .../app/admin-books/webapp/Component.js | 8 + .../admin-books/webapp/i18n/i18n.properties | 3 + .../webapp/i18n/i18n_de.properties | 3 + .../app/admin-books/webapp/manifest.json | 145 ++ .../app/appconfig/fioriSandboxConfig.json | 95 + sample-app/app/browse/fiori-service.cds | 56 + sample-app/app/browse/webapp/Component.js | 7 + .../app/browse/webapp/i18n/i18n.properties | 3 + .../app/browse/webapp/i18n/i18n_de.properties | 3 + sample-app/app/browse/webapp/manifest.json | 137 ++ sample-app/app/common.cds | 264 +++ sample-app/app/index.html | 32 + sample-app/app/services.cds | 6 + .../db/data/sap.capire.bookshop-Authors.csv | 5 + .../db/data/sap.capire.bookshop-Books.csv | 6 + .../data/sap.capire.bookshop-Books_texts.csv | 5 + .../db/data/sap.capire.bookshop-Genres.csv | 16 + sample-app/db/schema.cds | 37 + sample-app/package-lock.json | 1878 +++++++++++++++++ sample-app/package.json | 7 + sample-app/pom.xml | 146 ++ sample-app/srv/_i18n/i18n.properties | 18 + sample-app/srv/_i18n/i18n_de.properties | 18 + sample-app/srv/admin-service.cds | 35 + sample-app/srv/cat-service.cds | 31 + sample-app/srv/notifications.cds | 89 + sample-app/srv/pom.xml | 159 ++ .../java/customer/sample_app/Application.java | 13 + .../config/DestinationConfiguration.java | 47 + .../handlers/AdminServiceHandler.java | 41 + .../handlers/CatalogServiceHandler.java | 82 + .../srv/src/main/resources/application.yaml | 59 + .../email-templates/book-ordered.html | 38 + .../handlers/CatalogServiceHandlerTest.java | 42 + 41 files changed, 4065 insertions(+) create mode 100644 sample-app/.cdsrc.json create mode 100644 sample-app/.gitignore create mode 100644 sample-app/META-INF/cds4j-codegen/services.generated create mode 100644 sample-app/README.md create mode 100644 sample-app/app/_i18n/i18n.properties create mode 100644 sample-app/app/_i18n/i18n_de.properties create mode 100644 sample-app/app/admin-books/fiori-service.cds create mode 100644 sample-app/app/admin-books/webapp/Component.js create mode 100644 sample-app/app/admin-books/webapp/i18n/i18n.properties create mode 100644 sample-app/app/admin-books/webapp/i18n/i18n_de.properties create mode 100644 sample-app/app/admin-books/webapp/manifest.json create mode 100644 sample-app/app/appconfig/fioriSandboxConfig.json create mode 100644 sample-app/app/browse/fiori-service.cds create mode 100644 sample-app/app/browse/webapp/Component.js create mode 100644 sample-app/app/browse/webapp/i18n/i18n.properties create mode 100644 sample-app/app/browse/webapp/i18n/i18n_de.properties create mode 100644 sample-app/app/browse/webapp/manifest.json create mode 100644 sample-app/app/common.cds create mode 100644 sample-app/app/index.html create mode 100644 sample-app/app/services.cds create mode 100644 sample-app/db/data/sap.capire.bookshop-Authors.csv create mode 100644 sample-app/db/data/sap.capire.bookshop-Books.csv create mode 100644 sample-app/db/data/sap.capire.bookshop-Books_texts.csv create mode 100644 sample-app/db/data/sap.capire.bookshop-Genres.csv create mode 100644 sample-app/db/schema.cds create mode 100644 sample-app/package-lock.json create mode 100644 sample-app/package.json create mode 100644 sample-app/pom.xml create mode 100644 sample-app/srv/_i18n/i18n.properties create mode 100644 sample-app/srv/_i18n/i18n_de.properties create mode 100644 sample-app/srv/admin-service.cds create mode 100644 sample-app/srv/cat-service.cds create mode 100644 sample-app/srv/notifications.cds create mode 100644 sample-app/srv/pom.xml create mode 100644 sample-app/srv/src/main/java/customer/sample_app/Application.java create mode 100644 sample-app/srv/src/main/java/customer/sample_app/config/DestinationConfiguration.java create mode 100644 sample-app/srv/src/main/java/customer/sample_app/handlers/AdminServiceHandler.java create mode 100644 sample-app/srv/src/main/java/customer/sample_app/handlers/CatalogServiceHandler.java create mode 100644 sample-app/srv/src/main/resources/application.yaml create mode 100644 sample-app/srv/src/main/resources/email-templates/book-ordered.html create mode 100644 sample-app/srv/src/test/java/customer/sample_app/handlers/CatalogServiceHandlerTest.java diff --git a/sample-app/.cdsrc.json b/sample-app/.cdsrc.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/sample-app/.cdsrc.json @@ -0,0 +1,2 @@ +{ +} diff --git a/sample-app/.gitignore b/sample-app/.gitignore new file mode 100644 index 0000000..c161f22 --- /dev/null +++ b/sample-app/.gitignore @@ -0,0 +1,31 @@ +**/gen/ +**/edmx/ +*.db +*.sqlite +*.sqlite-wal +*.sqlite-shm +schema*.sql +default-env.json + +**/bin/ +**/target/ +.flattened-pom.xml +.classpath +.project +.settings + +**/node/ +**/node_modules/ + +**/.mta/ +*.mtar + +*.log* +gc_history* +hs_err* +*.tgz +*.iml + +.vscode +.idea +.reloadtrigger diff --git a/sample-app/META-INF/cds4j-codegen/services.generated b/sample-app/META-INF/cds4j-codegen/services.generated new file mode 100644 index 0000000..f7d1e75 --- /dev/null +++ b/sample-app/META-INF/cds4j-codegen/services.generated @@ -0,0 +1,3 @@ +cds.gen.notificationtypeproviderservice.NotificationTypeProviderService +cds.gen.notificationtemplateproviderservice.NotificationTemplateProviderService +cds.gen.notificationproviderservice.NotificationProviderService diff --git a/sample-app/README.md b/sample-app/README.md new file mode 100644 index 0000000..dd7e974 --- /dev/null +++ b/sample-app/README.md @@ -0,0 +1,339 @@ +# Bookshop Sample - Notifications Plugin + +This sample demonstrates how to use the `cds-feature-notifications` plugin in a CAP Java application. It extends the classic CAP bookshop sample to show different notification patterns supported by the plugin. + +## What This Sample Demonstrates + +- Manual notification via `NotificationService` from Java handler code +- Entity-based notifications via `@notifications` annotation on CDS entities +- CRUD triggers (`UPDATE`) and bound action triggers (`restock`) +- Dynamic priority expressions using CDS functions (`contains()`, ternary operator) +- i18n support for notification templates +- HTML email templates +- Delivery channel control (Mail only, Web only, Mail + Web) +- Semantic object navigation (deep link from notification to app) +- Customizable notification templates +- `where` conditions to filter when notifications are sent + +## Prerequisites + +- Java 17 or higher +- Maven 3.6 or higher +- Node.js 18 or higher +- npm +- CAP Java (`com.sap.cds:cds-services-bom`) **4.9.0 or higher** + +## Getting Started + +1. **Install the plugin to your local Maven repository** (only needed when running locally with a snapshot version): + ```bash + # From the project root + mvn install -pl cds-feature-notifications -DskipTests + ``` + +2. **Navigate to the sample**: + ```bash + cd sample-app + ``` + +3. **Build the application**: + ```bash + mvn clean compile + ``` + +4. **Run the application**: + ```bash + mvn spring-boot:run + ``` + +5. **Access the application**: + - Browse Books: http://localhost:8080/browse/index.html + - Admin Books: http://localhost:8080/admin-books/index.html + +## Notification Examples + +### Example 1: Manual Notification — `BookOrdered` + +**File:** `srv/notifications.cds`, `srv/src/main/java/.../handlers/CatalogServiceHandler.java` + +When a user submits an order via the `submitOrder` action, the Java handler manually emits a `BookOrdered` notification through the `NotificationService`. + +**Features demonstrated:** +- Manual emit from Java handler +- i18n template (`{i18n>BOOK_ORDERED_TEMPLATE_SENSITIVE}`) +- HTML email template (`email-templates/book-ordered.html`) +- Mail + Web delivery channels +- `@Common.SemanticObject` for deep link navigation +- `customizable: true` to allow end-user configuration +- Static priority `#HIGH` +- Single string recipient (`recipients: String`) + +```cds +@Common.SemanticObject : 'Books' +@Common.SemanticObjectAction: 'display' +@notification: { + customizable: true, + template: { + title : '{i18n>BOOK_ORDERED_TEMPLATE_SENSITIVE}', + publicTitle : '{i18n>BOOK_ORDERED_TEMPLATE_PUBLIC}', + subtitle : '{i18n>BOOK_ORDERED_SUBTITLE}', + groupedTitle: '{i18n>BOOK_ORDERED_TEMPLATE_GROUPED}', + email: { + subject: '{i18n>BOOK_ORDERED_EMAIL_SUBJECT}', + html : 'email-templates/book-ordered.html', + }, + }, + deliveryChannels: [ + { channel: #Mail, enabled: true, defaultPreference: true }, + { channel: #Web, enabled: true, defaultPreference: true }, + ], + priority: #HIGH, +} +event BookOrdered { + recipients : String; // single email or UUID + bookTitle : String; + quantity : Integer; + buyer : String; +} +``` + +```java +BookOrdered notification = BookOrdered.create(); +notification.setRecipients(context.getUserInfo().getName()); +notification.setBookTitle(book.getTitle()); +notification.setQuantity(context.getQuantity()); +notification.setBuyer(context.getUserInfo().getName()); + +BookOrderedContext notifContext = BookOrderedContext.create(); +notifContext.setData(notification); +notificationService.emit(notifContext); +``` + +--- + +### Example 2: Entity Change Notification — `LowStockAlert` + +**File:** `srv/notifications.cds`, `srv/admin-service.cds` + +Notifications are sent automatically by the plugin when the `Books` entity is updated and the stock drops below 10 — no Java handler code needed. The `@notifications` annotation on the entity declares the trigger condition, recipients, and parameter mapping. + +**Features demonstrated:** +- `@notifications` annotation on entity (declarative, no Java code) +- `UPDATE` CRUD trigger +- `where` condition to filter notifications (`$self.stock < 10`) +- Dynamic priority combining `contains()` and numeric comparison +- Email-only delivery channel (`#Mail`) +- Array of recipients (`recipients: array of String`) — multiple emails or UUIDs +- `$self.fieldName` for parameter mapping + +```cds +// Event definition in notifications.cds +@notification: { + template: { + title : 'Low stock alert for "{{bookTitle}}"', + publicTitle : 'Low stock alert', + subtitle : 'Remaining stock: {{stock}}', + groupedTitle : '{{count}} low stock alerts', + }, + deliveryChannels: [ + { channel: #Mail, enabled: true, defaultPreference: true } + ], + priority: (contains(bookTitle, 'Heights') ? 'HIGH' : (stock < 5 ? 'HIGH' : 'MEDIUM')), +} +event LowStockAlert { + recipients : array of String; + bookTitle : String; + stock : Integer; +} +``` + +```cds +// Entity annotation in admin-service.cds +@notifications: [{ + type : 'LowStockAlert', + on : ['UPDATE'], + recipients: $self.createdBy, + where : ($self.stock < 10), + parameters: { + bookTitle: $self.title, + stock : $self.stock, + } +}] +entity Books as projection on my.Books; +``` + +--- + +### Example 3: Bound Action Notification — `StockReplenished` + +**File:** `srv/notifications.cds`, `srv/admin-service.cds` + +Notifications are sent automatically when the `restock` bound action is called on a Book. This demonstrates how to trigger notifications from custom actions without writing any Java handler code. + +**Features demonstrated:** +- `@notifications` annotation with bound action trigger (`restock`) +- Web-only delivery (no `deliveryChannels` → ANS default: Web notification only, no email) +- Static priority `#MEDIUM` +- `$self.fieldName` for parameter mapping from the entity to notification properties + +```cds +// Event definition in notifications.cds +@notification: { + template: { + title : 'Stock replenished for "{{bookTitle}}"', + publicTitle : 'Stock replenished', + subtitle : 'New stock level: {{newStock}}', + groupedTitle : '{{count}} stock replenishments', + }, + priority: #MEDIUM, +} +event StockReplenished { + recipients : String; + bookTitle : String; + newStock : Integer; +} +``` + +```cds +// Entity annotation in admin-service.cds +@notifications: [{ + type : 'StockReplenished', + on : ['restock'], + recipients: $self.createdBy, + parameters: { + bookTitle: $self.title, + newStock : $self.stock, + } +}] +entity Books as projection on my.Books + actions { + action restock(amount : Integer) returns Books; + }; +``` + +--- + +## Implementation Details + +### Maven Configuration + +The notifications plugin is added to `srv/pom.xml`: + +```xml + + com.sap.cds + cds-feature-notifications + +``` + +Also add excludes for the plugin's remote service models inside the `cds-maven-plugin` configuration, so they are not generated in your project: + +```xml + + + NotificationProviderService.** + NotificationProviderService + NotificationTypeProviderService.** + NotificationTypeProviderService + NotificationTemplateProviderService.** + NotificationTemplateProviderService + +``` + +### CDS Model + +The sample uses two CDS files for notifications: + +- **`srv/notifications.cds`** — defines notification events with `@notification` annotations (template, priority, delivery channels) +- **`srv/admin-service.cds`** — uses `@notifications` on the `Books` entity to declaratively trigger notifications on CRUD/action events + +```cds +// srv/notifications.cds — event definitions +service NotificationService { + @notification: { template: {...}, deliveryChannels: [...], priority: #HIGH } + event BookOrdered { recipients: String; bookTitle: String; ... } + + @notification: { template: {...}, deliveryChannels: [{ channel: #Mail, ... }], priority: (...) } + event LowStockAlert { recipients: array of String; bookTitle: String; stock: Integer; } + + @notification: { template: {...}, priority: #MEDIUM } + event StockReplenished { recipients: String; bookTitle: String; newStock: Integer; } +} +``` + +```cds +// srv/admin-service.cds — declarative triggers +@notifications: [ + { type: 'LowStockAlert', on: ['UPDATE'], where: ($self.stock < 10), ... }, + { type: 'StockReplenished', on: ['restock'], ... } +] +entity Books as projection on my.Books actions { action restock(...); }; +``` + +### i18n Support + +Notification templates support internationalization. Translation files are located in `srv/_i18n/`: + +| File | Language | +|---|---| +| `i18n.properties` | Default (English) | +| `i18n_de.properties` | German | + +### HTML Email Template + +The `BookOrdered` notification uses an HTML email template located at `srv/src/main/resources/email-templates/book-ordered.html`. The template uses `{i18n>KEY}` for translated strings and `{{fieldName}}` for dynamic values from the event payload. + +## Local Mode vs Production Mode + +The plugin automatically switches between two modes based on your `application.yaml` configuration. + +### Local Mode (default) + +No configuration needed. Notifications are logged to the console instead of being sent to SAP Alert Notification Service. This is the default when running locally. + +```yaml +# application.yaml — local mode (default, no extra config needed) +``` + +You will see log output like: +``` +INFO - [LocalHandler] Notification sent: BookOrdered → recipient: admin +``` + +### Production Mode + +To send notifications to SAP Alert Notification Service (and route them to SAP Build Work Zone), enable production mode in `application.yaml`: + +```yaml +cds: + environment: + production: + enabled: true +``` + +In production mode, the plugin needs ANS credentials. There are two ways to provide them: + +**Option 1: Bind an ANS service instance on Cloud Foundry** + +Create an ANS service instance with the `business-notifications` plan and bind it to the app. The plugin auto-discovers the binding. + +**Option 2: Local testing with manual destination registration** + +For local testing against a real ANS instance, use the `DestinationConfiguration.java` template at `srv/src/main/java/customer/sample_app/config/`. The file is fully commented out — to use it: + +1. Uncomment the class body +2. Fill in your ANS service key values: + - `clientId`, `clientSecret` — from the ANS service key + - `tokenUrl` — OAuth2 token endpoint + - `host` — ANS API URL + - `destinationName` — must be `SAP_Notifications` (matches the plugin's expected destination name) +3. Run with `cds.environment.production.enabled: true` + +## Troubleshooting + +- **Port conflicts**: If port 8080 is in use, specify a different port: `mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8081"` +- **Plugin not found**: Make sure you ran `mvn install -pl cds-feature-notifications -DskipTests` from the project root first + +## Advanced Topics + +For advanced topics like production ANS configuration, recipient formats, dynamic priority expressions, language resolution via IAS destination, template customization, batch notifications, and outbox support, see the [main project documentation](../README.md). diff --git a/sample-app/app/_i18n/i18n.properties b/sample-app/app/_i18n/i18n.properties new file mode 100644 index 0000000..7326bbb --- /dev/null +++ b/sample-app/app/_i18n/i18n.properties @@ -0,0 +1,15 @@ +Books = Books +Book = Book +ID = ID +Title = Title +Author = Author +Authors = Authors +AuthorID = Author ID +AuthorName = Author Name +Name = Name +Age = Age +Stock = Stock +Order = Order +Orders = Orders +Price = Price +Genre = Genre \ No newline at end of file diff --git a/sample-app/app/_i18n/i18n_de.properties b/sample-app/app/_i18n/i18n_de.properties new file mode 100644 index 0000000..cb712c1 --- /dev/null +++ b/sample-app/app/_i18n/i18n_de.properties @@ -0,0 +1,15 @@ +Books = Bücher +Book = Buch +ID = ID +Title = Titel +Author = Autor +Authors = Autoren +AuthorID = ID des Autors +AuthorName = Name des Autors +Name = Name +Age = Alter +Stock = Bestand +Order = Bestellung +Orders = Bestellungen +Price = Preis +Genre = Genre \ No newline at end of file diff --git a/sample-app/app/admin-books/fiori-service.cds b/sample-app/app/admin-books/fiori-service.cds new file mode 100644 index 0000000..7bc87f7 --- /dev/null +++ b/sample-app/app/admin-books/fiori-service.cds @@ -0,0 +1,126 @@ +using {AdminService} from '../../srv/admin-service.cds'; + +//////////////////////////////////////////////////////////////////////////// +// +// Books Object Page +// +annotate AdminService.Books with @(UI: { + HeaderInfo : { + TypeName : '{i18n>Book}', + TypeNamePlural: '{i18n>Books}', + Title : {Value: title}, + Description : {Value: author.name} + }, + Facets : [ + { + $Type : 'UI.ReferenceFacet', + Label : '{i18n>General}', + Target: '@UI.FieldGroup#General' + }, + { + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Translations}', + Target: 'texts/@UI.LineItem' + }, + { + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Details}', + Target: '@UI.FieldGroup#Details' + }, + { + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Admin}', + Target: '@UI.FieldGroup#Admin' + } + ], + Identification : [{ + $Type : 'UI.DataFieldForAction', + Action : 'AdminService.restock', + Label : 'Restock' + }], + FieldGroup #General: {Data: [ + {Value: title}, + {Value: author_ID}, + {Value: genre_ID}, + {Value: descr} + ]}, + FieldGroup #Details: {Data: [ + {Value: stock}, + {Value: price} + ]}, + FieldGroup #Admin : {Data: [ + {Value: createdBy}, + {Value: createdAt}, + {Value: modifiedBy}, + {Value: modifiedAt} + ]} +}); + + +//////////////////////////////////////////////////////////// +// +// Draft for Localized Data +// +annotate sap.capire.bookshop.Books with @fiori.draft.enabled; +annotate AdminService.Books with @odata.draft.enabled; + +annotate AdminService.Books with @(UI: { + LineItem: [ + {Value: title}, + {Value: stock}, + {Value: price} + ] +}); + +annotate AdminService.Books.texts with @(UI: { + Identification : [{Value: title}], + SelectionFields: [ + locale, + title + ], + LineItem : [ + { + Value: locale, + Label: 'Locale' + }, + { + Value: title, + Label: 'Title' + }, + { + Value: descr, + Label: 'Description' + } + ] +}); + +annotate AdminService.Books.texts with { + ID @UI.Hidden; + ID_texts @UI.Hidden; +}; + +// Add Value Help for Locales +annotate AdminService.Books.texts { + locale @( + ValueList.entity: 'Languages', + Common.ValueListWithFixedValues //show as drop down, not a dialog + ) +}; + +// In addition we need to expose Languages through AdminService as a target for ValueList +using {sap} from '@sap/cds/common'; + +extend service AdminService { + @readonly + entity Languages as projection on sap.common.Languages; +} + +// Workaround for Fiori popup for asking user to enter a new UUID on Create +annotate AdminService.Books with { + ID @Core.Computed; +} + +// Show Genre as drop down, not a dialog +annotate AdminService.Books with { + genre @Common.ValueListWithFixedValues; +} diff --git a/sample-app/app/admin-books/webapp/Component.js b/sample-app/app/admin-books/webapp/Component.js new file mode 100644 index 0000000..e98677e --- /dev/null +++ b/sample-app/app/admin-books/webapp/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/fe/core/AppComponent"], function (AppComponent) { + "use strict"; + return AppComponent.extend("books.Component", { + metadata: { manifest: "json" } + }); +}); + +/* eslint no-undef:0 */ diff --git a/sample-app/app/admin-books/webapp/i18n/i18n.properties b/sample-app/app/admin-books/webapp/i18n/i18n.properties new file mode 100644 index 0000000..9a23ee4 --- /dev/null +++ b/sample-app/app/admin-books/webapp/i18n/i18n.properties @@ -0,0 +1,3 @@ +appTitle=Manage Books +appSubTitle=Manage bookshop inventory +appDescription=Manage your bookshop inventory with ease. diff --git a/sample-app/app/admin-books/webapp/i18n/i18n_de.properties b/sample-app/app/admin-books/webapp/i18n/i18n_de.properties new file mode 100644 index 0000000..01d56a2 --- /dev/null +++ b/sample-app/app/admin-books/webapp/i18n/i18n_de.properties @@ -0,0 +1,3 @@ +appTitle=Bücher verwalten +appSubTitle=Verwalten Sie den Bestand der Buchhandlungen +appDescription=Verwalten Sie den Bestand Ihrer Buchhandlung ganz einfach. diff --git a/sample-app/app/admin-books/webapp/manifest.json b/sample-app/app/admin-books/webapp/manifest.json new file mode 100644 index 0000000..880b893 --- /dev/null +++ b/sample-app/app/admin-books/webapp/manifest.json @@ -0,0 +1,145 @@ +{ + "_version": "1.49.0", + "sap.app": { + "applicationVersion": { + "version": "1.0.0" + }, + "id": "sample-app.admin-books", + "type": "application", + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "i18n": "i18n/i18n.properties", + "dataSources": { + "AdminService": { + "uri": "/odata/v4/AdminService/", + "type": "OData", + "settings": { + "odataVersion": "4.0" + } + } + }, + "crossNavigation": { + "inbounds": { + "intent-Books-manage": { + "signature": { + "parameters": {}, + "additionalParameters": "allowed" + }, + "semanticObject": "Books", + "action": "manage" + } + } + } + }, + "sap.ui": { + "technology": "UI5", + "fullWidth": false, + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.115.1", + "libs": { + "sap.fe.templates": {} + } + }, + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "uri": "i18n/i18n.properties" + }, + "": { + "dataSource": "AdminService", + "settings": { + "operationMode": "Server", + "autoExpandSelect": true, + "earlyRequests": true, + "groupProperties": { + "default": { + "submit": "Auto" + } + } + } + } + }, + "routing": { + "routes": [ + { + "pattern": ":?query:", + "name": "BooksList", + "target": "BooksList" + }, + { + "pattern": "Books({key}):?query:", + "name": "BooksDetails", + "target": "BooksDetails" + }, + { + "pattern": "Books({key}/author({key2}):?query:", + "name": "AuthorsDetails", + "target": "AuthorsDetails" + } + ], + "targets": { + "BooksList": { + "type": "Component", + "id": "BooksList", + "name": "sap.fe.templates.ListReport", + "options": { + "settings": { + "contextPath": "/Books", + "initialLoad": true, + "navigation": { + "Books": { + "detail": { + "route": "BooksDetails" + } + } + } + } + } + }, + "BooksDetails": { + "type": "Component", + "id": "BooksDetailsList", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "contextPath": "/Books", + "editableHeaderContent": false, + "navigation": { + "Authors": { + "detail": { + "route": "AuthorsDetails" + } + } + } + } + } + }, + "AuthorsDetails": { + "type": "Component", + "id": "AuthorsDetailsList", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "contextPath": "/Authors" + } + } + } + } + }, + "contentDensities": { + "compact": true, + "cozy": true + } + }, + "sap.fiori": { + "registrationIds": [], + "archeType": "transactional" + } +} diff --git a/sample-app/app/appconfig/fioriSandboxConfig.json b/sample-app/app/appconfig/fioriSandboxConfig.json new file mode 100644 index 0000000..ff2ac49 --- /dev/null +++ b/sample-app/app/appconfig/fioriSandboxConfig.json @@ -0,0 +1,95 @@ +{ + "services": { + "LaunchPage": { + "adapter": { + "config": { + "catalogs": [], + "groups": [ + { + "id": "Bookshop", + "title": "Bookshop", + "isPreset": true, + "isVisible": true, + "isGroupLocked": false, + "tiles": [ + { + "id": "BrowseBooks", + "tileType": "sap.ushell.ui.tile.StaticTile", + "properties": { + "title": "Browse Books", + "targetURL": "#Books-display" + } + } + ] + }, + { + "id": "Administration", + "title": "Administration", + "isPreset": true, + "isVisible": true, + "isGroupLocked": false, + "tiles": [ + { + "id": "ManageBooks", + "tileType": "sap.ushell.ui.tile.StaticTile", + "properties": { + "title": "Manage Books", + "targetURL": "#Books-manage" + } + } + ] + } + ] + } + } + }, + "NavTargetResolution": { + "config": { + "enableClientSideTargetResolution": true + } + }, + "ClientSideTargetResolution": { + "adapter": { + "config": { + "inbounds": { + "BrowseBooks": { + "semanticObject": "Books", + "action": "display", + "title": "Browse Books", + "signature": { + "parameters": { + "Books.ID": { + "renameTo": "ID" + }, + "Authors.books.ID": { + "renameTo": "ID" + } + }, + "additionalParameters": "ignored" + }, + "resolutionResult": { + "applicationType": "SAPUI5", + "additionalInformation": "SAPUI5.Component=bookshop", + "url": "browse/webapp" + } + }, + "ManageBooks": { + "semanticObject": "Books", + "action": "manage", + "title": "Manage Books", + "signature": { + "parameters": {}, + "additionalParameters": "allowed" + }, + "resolutionResult": { + "applicationType": "SAPUI5", + "additionalInformation": "SAPUI5.Component=books", + "url": "admin-books/webapp" + } + } + } + } + } + } + } +} diff --git a/sample-app/app/browse/fiori-service.cds b/sample-app/app/browse/fiori-service.cds new file mode 100644 index 0000000..2189d3c --- /dev/null +++ b/sample-app/app/browse/fiori-service.cds @@ -0,0 +1,56 @@ +using {CatalogService} from '../../srv/cat-service.cds'; + +//////////////////////////////////////////////////////////////////////////// +// +// Books Object Page +// +annotate CatalogService.Books with @(UI: { + HeaderInfo : { + TypeName : '{i18n>Book}', + TypeNamePlural: '{i18n>Books}', + Title : {Value: title}, + Description : {Value: author} + }, + HeaderFacets : [{ + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Description}', + Target: '@UI.FieldGroup#Descr' + }], + Facets : [{ + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Details}', + Target: '@UI.FieldGroup#Price' + }], + FieldGroup #Descr: {Data: [{Value: descr}]}, + FieldGroup #Price: {Data: [{Value: price}]}, + Identification : [{ + $Type : 'UI.DataFieldForAction', + Action: 'CatalogService.submitOrder', + Label : 'Submit Order' + }] +}); + +//////////////////////////////////////////////////////////////////////////// +// +// Books List Page +// +annotate CatalogService.Books with @(UI: { + SelectionFields: [ + ID, + price, + currency_code + ], + LineItem : [ + { + Value: ID, + Label: '{i18n>Title}' + }, + { + Value: author, + Label: '{i18n>Author}' + }, + {Value: genre.name}, + {Value: price}, + {Value: currency.symbol} + ] +}); diff --git a/sample-app/app/browse/webapp/Component.js b/sample-app/app/browse/webapp/Component.js new file mode 100644 index 0000000..4020679 --- /dev/null +++ b/sample-app/app/browse/webapp/Component.js @@ -0,0 +1,7 @@ +sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) { + "use strict"; + return AppComponent.extend("bookshop.Component", { + metadata: { manifest: "json" } + }); +}); +/* eslint no-undef:0 */ diff --git a/sample-app/app/browse/webapp/i18n/i18n.properties b/sample-app/app/browse/webapp/i18n/i18n.properties new file mode 100644 index 0000000..21436e8 --- /dev/null +++ b/sample-app/app/browse/webapp/i18n/i18n.properties @@ -0,0 +1,3 @@ +appTitle=Browse Books +appSubTitle=Find all your favorite books +appDescription=This application lets you find the next books you want to read. diff --git a/sample-app/app/browse/webapp/i18n/i18n_de.properties b/sample-app/app/browse/webapp/i18n/i18n_de.properties new file mode 100644 index 0000000..ea86c3f --- /dev/null +++ b/sample-app/app/browse/webapp/i18n/i18n_de.properties @@ -0,0 +1,3 @@ +appTitle=Bücher anschauen +appSubTitle=Finden sie ihre nächste Lektüre +appDescription=Finden Sie die nachsten Bücher, die Sie lesen möchten. diff --git a/sample-app/app/browse/webapp/manifest.json b/sample-app/app/browse/webapp/manifest.json new file mode 100644 index 0000000..33d4b6a --- /dev/null +++ b/sample-app/app/browse/webapp/manifest.json @@ -0,0 +1,137 @@ +{ + "_version": "1.49.0", + "sap.app": { + "id": "sample-app.browse", + "applicationVersion": { + "version": "1.0.0" + }, + "type": "application", + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "i18n": "i18n/i18n.properties", + "dataSources": { + "CatalogService": { + "uri": "/odata/v4/CatalogService/", + "type": "OData", + "settings": { + "odataVersion": "4.0" + } + } + }, + "crossNavigation": { + "inbounds": { + "intent1": { + "signature": { + "parameters": { + "Books.ID": { + "renameTo": "ID" + }, + "Authors.books.ID": { + "renameTo": "ID" + } + }, + "additionalParameters": "ignored" + }, + "semanticObject": "Books", + "action": "display", + "title": "{{appTitle}}", + "subTitle": "{{appSubTitle}}", + "icon": "sap-icon://course-book", + "indicatorDataSource": { + "dataSource": "CatalogService", + "path": "Books/$count", + "refresh": 1800 + } + } + } + } + }, + "sap.ui": { + "technology": "UI5", + "fullWidth": false, + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.115.1", + "libs": { + "sap.fe.templates": {} + } + }, + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "uri": "i18n/i18n.properties" + }, + "": { + "dataSource": "CatalogService", + "settings": { + "operationMode": "Server", + "autoExpandSelect": true, + "earlyRequests": true, + "groupProperties": { + "default": { + "submit": "Auto" + } + } + } + } + }, + "routing": { + "routes": [ + { + "pattern": ":?query:", + "name": "BooksList", + "target": "BooksList" + }, + { + "pattern": "Books({key}):?query:", + "name": "BooksDetails", + "target": "BooksDetails" + } + ], + "targets": { + "BooksList": { + "type": "Component", + "id": "BooksList", + "name": "sap.fe.templates.ListReport", + "options": { + "settings": { + "contextPath": "/Books", + "initialLoad": true, + "navigation": { + "Books": { + "detail": { + "route": "BooksDetails" + } + } + } + } + } + }, + "BooksDetails": { + "type": "Component", + "id": "BooksDetailsList", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "contextPath": "/Books" + } + } + } + } + }, + "contentDensities": { + "compact": true, + "cozy": true + } + }, + "sap.fiori": { + "registrationIds": [], + "archeType": "transactional" + } +} diff --git a/sample-app/app/common.cds b/sample-app/app/common.cds new file mode 100644 index 0000000..69627be --- /dev/null +++ b/sample-app/app/common.cds @@ -0,0 +1,264 @@ +/* + Common Annotations shared by all apps +*/ + +using {sap.capire.bookshop as my} from '../db/schema'; +using { + sap.common, + sap.common.Currencies +} from '@sap/cds/common'; + +//////////////////////////////////////////////////////////////////////////// +// +// Books Lists +// +annotate my.Books with @( + Common.SemanticKey: [ID], + UI : { + Identification : [{Value: title}], + SelectionFields: [ + ID, + author_ID, + price, + currency_code + ], + LineItem : [ + { + Value: ID, + Label: '{i18n>Title}' + }, + { + Value: author.ID, + Label: '{i18n>Author}' + }, + {Value: genre.name}, + {Value: stock}, + {Value: price}, + {Value: currency.symbol} + ] + } +) { + ID @Common : { + SemanticObject : 'Books', + Text : title, + TextArrangement: #TextOnly + }; + author @ValueList.entity: 'Authors'; +}; + +annotate Currencies with { + symbol @Common.Label: '{i18n>Currency}'; +} + +//////////////////////////////////////////////////////////////////////////// +// +// Books Elements +// +annotate my.Books with { + ID @title: '{i18n>ID}'; + title @title: '{i18n>Title}'; + genre @title: '{i18n>Genre}' @Common : { + Text : genre.name, + TextArrangement: #TextOnly + }; + author @title: '{i18n>Author}' @Common : { + Text : author.name, + TextArrangement: #TextOnly + }; + price @title: '{i18n>Price}' @Measures.ISOCurrency: currency_code; + stock @title: '{i18n>Stock}'; + descr @title: '{i18n>Description}' @UI.MultiLineText; + image @title: '{i18n>Image}'; +} + +//////////////////////////////////////////////////////////////////////////// +// +// Genres List +// +annotate my.Genres with @( + Common.SemanticKey: [name], + UI : { + SelectionFields: [name], + LineItem : [ + {Value: name}, + { + Value: parent.name, + Label: 'Main Genre' + } + ] + } +); + +annotate my.Genres with { + ID @Common.Text: name @Common.TextArrangement: #TextOnly; +} + +//////////////////////////////////////////////////////////////////////////// +// +// Genre Details +// +annotate my.Genres with @(UI: { + Identification: [{Value: name}], + HeaderInfo : { + TypeName : '{i18n>Genre}', + TypeNamePlural: '{i18n>Genres}', + Title : {Value: name}, + Description : {Value: ID} + }, + Facets : [{ + $Type : 'UI.ReferenceFacet', + Label : '{i18n>SubGenres}', + Target: 'children/@UI.LineItem' + }] +}); + +//////////////////////////////////////////////////////////////////////////// +// +// Genres Elements +// +annotate my.Genres with { + ID @title: '{i18n>ID}'; + name @title: '{i18n>Genre}'; +} + +//////////////////////////////////////////////////////////////////////////// +// +// Authors List +// +annotate my.Authors with @( + Common.SemanticKey: [ID], + UI : { + Identification : [{Value: name}], + SelectionFields: [name], + LineItem : [ + {Value: ID}, + {Value: dateOfBirth}, + {Value: dateOfDeath}, + {Value: placeOfBirth}, + {Value: placeOfDeath} + ] + } +) { + ID @Common: { + SemanticObject : 'Authors', + Text : name, + TextArrangement: #TextOnly + }; +}; + +//////////////////////////////////////////////////////////////////////////// +// +// Author Details +// +annotate my.Authors with @(UI: { + HeaderInfo: { + TypeName : '{i18n>Author}', + TypeNamePlural: '{i18n>Authors}', + Title : {Value: name}, + Description : {Value: dateOfBirth} + }, + Facets : [{ + $Type : 'UI.ReferenceFacet', + Target: 'books/@UI.LineItem' + }] +}); + + +//////////////////////////////////////////////////////////////////////////// +// +// Authors Elements +// +annotate my.Authors with { + ID @title: '{i18n>ID}'; + name @title: '{i18n>Name}'; + dateOfBirth @title: '{i18n>DateOfBirth}'; + dateOfDeath @title: '{i18n>DateOfDeath}'; + placeOfBirth @title: '{i18n>PlaceOfBirth}'; + placeOfDeath @title: '{i18n>PlaceOfDeath}'; +} + +//////////////////////////////////////////////////////////////////////////// +// +// Languages List +// +annotate common.Languages with @( + Common.SemanticKey: [code], + Identification : [{Value: code}], + UI : { + SelectionFields: [ + name, + descr + ], + LineItem : [ + {Value: code}, + {Value: name} + ] + } +); + +//////////////////////////////////////////////////////////////////////////// +// +// Language Details +// +annotate common.Languages with @(UI: { + HeaderInfo : { + TypeName : '{i18n>Language}', + TypeNamePlural: '{i18n>Languages}', + Title : {Value: name}, + Description : {Value: descr} + }, + Facets : [{ + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Details}', + Target: '@UI.FieldGroup#Details' + }], + FieldGroup #Details: {Data: [ + {Value: code}, + {Value: name}, + {Value: descr} + ]} +}); + +//////////////////////////////////////////////////////////////////////////// +// +// Currencies List +// +annotate common.Currencies with @( + Common.SemanticKey: [code], + Identification : [{Value: code}], + UI : { + SelectionFields: [ + name, + descr + ], + LineItem : [ + {Value: descr}, + {Value: symbol}, + {Value: code} + ] + } +); + +//////////////////////////////////////////////////////////////////////////// +// +// Currency Details +// +annotate common.Currencies with @(UI: { + HeaderInfo : { + TypeName : '{i18n>Currency}', + TypeNamePlural: '{i18n>Currencies}', + Title : {Value: descr}, + Description : {Value: code} + }, + Facets : [{ + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Details}', + Target: '@UI.FieldGroup#Details' + }], + FieldGroup #Details: {Data: [ + {Value: name}, + {Value: symbol}, + {Value: code}, + {Value: descr} + ]} +}); diff --git a/sample-app/app/index.html b/sample-app/app/index.html new file mode 100644 index 0000000..70f6315 --- /dev/null +++ b/sample-app/app/index.html @@ -0,0 +1,32 @@ + + + + + + + + Bookshop + + + + + + + + + + diff --git a/sample-app/app/services.cds b/sample-app/app/services.cds new file mode 100644 index 0000000..87e7b31 --- /dev/null +++ b/sample-app/app/services.cds @@ -0,0 +1,6 @@ +/* + This model controls what gets served to Fiori frontends... +*/ +using from './common'; +using from './browse/fiori-service'; +using from './admin-books/fiori-service'; diff --git a/sample-app/db/data/sap.capire.bookshop-Authors.csv b/sample-app/db/data/sap.capire.bookshop-Authors.csv new file mode 100644 index 0000000..5272ee1 --- /dev/null +++ b/sample-app/db/data/sap.capire.bookshop-Authors.csv @@ -0,0 +1,5 @@ +ID;name;dateOfBirth;placeOfBirth;dateOfDeath;placeOfDeath +10fef92e-975f-4c41-8045-c58e5c27a040;Emily Brontë;1818-07-30;Thornton, Yorkshire;1848-12-19;Haworth, Yorkshire +d4585e0e-ab3b-4424-b2ac-f2bfa785f068;Charlotte Brontë;1818-04-21;Thornton, Yorkshire;1855-03-31;Haworth, Yorkshire +4cf60975-300d-4dbe-8598-57b02e62bae2;Edgar Allen Poe;1809-01-19;Boston, Massachusetts;1849-10-07;Baltimore, Maryland +df9fb9fa-f121-45b5-8be5-8ff7ad5219a2;Richard Carpenter;1929-08-14;King’s Lynn, Norfolk;2012-02-26;Hertfordshire, England diff --git a/sample-app/db/data/sap.capire.bookshop-Books.csv b/sample-app/db/data/sap.capire.bookshop-Books.csv new file mode 100644 index 0000000..46d63fa --- /dev/null +++ b/sample-app/db/data/sap.capire.bookshop-Books.csv @@ -0,0 +1,6 @@ +ID;title;descr;author_ID;stock;price;currency_code;genre_ID +aeeda49f-72f2-4880-be27-a513b2e53040;Wuthering Heights;"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym ""Ellis Bell"". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.";10fef92e-975f-4c41-8045-c58e5c27a040;12;11.11;GBP;11 +b0056977-4cf5-46a2-ab14-6409ee2e0df1;Jane Eyre;"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name ""Currer Bell"", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.";d4585e0e-ab3b-4424-b2ac-f2bfa785f068;11;12.34;GBP;11 +c7641340-a9be-4673-8dad-785a2505f46e;The Raven;"""The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.";4cf60975-300d-4dbe-8598-57b02e62bae2;333;13.13;USD;16 +7756b725-cefc-43a2-a3c8-0c9104a349b8;Eleonora;"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.";4cf60975-300d-4dbe-8598-57b02e62bae2;555;14;USD;16 +a009c640-434a-4542-ac68-51b400c880ea;Catweazle;Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.;df9fb9fa-f121-45b5-8be5-8ff7ad5219a2;22;150;JPY;13 diff --git a/sample-app/db/data/sap.capire.bookshop-Books_texts.csv b/sample-app/db/data/sap.capire.bookshop-Books_texts.csv new file mode 100644 index 0000000..3a3465b --- /dev/null +++ b/sample-app/db/data/sap.capire.bookshop-Books_texts.csv @@ -0,0 +1,5 @@ +ID_texts;ID;locale;title;descr +52eee553-266d-4fdd-a5ca-909910e76ae4;aeeda49f-72f2-4880-be27-a513b2e53040;de;Sturmhöhe;Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman der englischen Schriftstellerin Emily Brontë (1818–1848). Der 1847 unter dem Pseudonym Ellis Bell veröffentlichte Roman wurde vom viktorianischen Publikum weitgehend abgelehnt, heute gilt er als ein Klassiker der britischen Romanliteratur des 19. Jahrhunderts. +54e58142-f06e-49c1-a51d-138f86cea34e;aeeda49f-72f2-4880-be27-a513b2e53040;fr;Les Hauts de Hurlevent;Les Hauts de Hurlevent (titre original : Wuthering Heights), parfois orthographié Les Hauts de Hurle-Vent, est l'unique roman d'Emily Brontë, publié pour la première fois en 1847 sous le pseudonyme d’Ellis Bell. Loin d'être un récit moralisateur, Emily Brontë achève néanmoins le roman dans une atmosphère sereine, suggérant le triomphe de la paix et du Bien sur la vengeance et le Mal. +bbbf8a88-797d-4790-af1c-1cc857718ee0;b0056977-4cf5-46a2-ab14-6409ee2e0df1;de;Jane Eyre;Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography), erstmals erschienen im Jahr 1847 unter dem Pseudonym Currer Bell, ist der erste veröffentlichte Roman der britischen Autorin Charlotte Brontë und ein Klassiker der viktorianischen Romanliteratur des 19. Jahrhunderts. Der Roman erzählt in Form einer Ich-Erzählung die Lebensgeschichte von Jane Eyre (ausgesprochen /ˌdʒeɪn ˈɛə/), die nach einer schweren Kindheit eine Stelle als Gouvernante annimmt und sich in ihren Arbeitgeber verliebt, jedoch immer wieder um ihre Freiheit und Selbstbestimmung kämpfen muss. Als klein, dünn, blass, stets schlicht dunkel gekleidet und mit strengem Mittelscheitel beschrieben, gilt die Heldin des Romans Jane Eyre nicht zuletzt aufgrund der Kino- und Fernsehversionen der melodramatischen Romanvorlage als die bekannteste englische Gouvernante der Literaturgeschichte +a90d4378-1a3e-48e7-b60b-5670e78807e1;7756b725-cefc-43a2-a3c8-0c9104a349b8;de;Eleonora;“Eleonora” ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841 erstveröffentlicht. In ihr geht es um das Paradox der Treue in der Treulosigkeit. diff --git a/sample-app/db/data/sap.capire.bookshop-Genres.csv b/sample-app/db/data/sap.capire.bookshop-Genres.csv new file mode 100644 index 0000000..1ea3793 --- /dev/null +++ b/sample-app/db/data/sap.capire.bookshop-Genres.csv @@ -0,0 +1,16 @@ +ID;parent_ID;name +10;;Fiction +11;10;Drama +12;10;Poetry +13;10;Fantasy +14;10;Science Fiction +15;10;Romance +16;10;Mystery +17;10;Thriller +18;10;Dystopia +19;10;Fairy Tale +20;;Non-Fiction +21;20;Biography +22;21;Autobiography +23;20;Essay +24;20;Speech diff --git a/sample-app/db/schema.cds b/sample-app/db/schema.cds new file mode 100644 index 0000000..1aedfba --- /dev/null +++ b/sample-app/db/schema.cds @@ -0,0 +1,37 @@ +using { + Currency, + managed, + cuid, + sap.common.CodeList +} from '@sap/cds/common'; + +namespace sap.capire.bookshop; + +entity Books : managed, cuid { + @mandatory title : localized String(111); + descr : localized String(1111); + @mandatory author : Association to Authors; + genre : Association to Genres; + stock : Integer; + price : Decimal; + currency : Currency; + image : LargeBinary @Core.MediaType: 'image/png'; +} + +entity Authors : managed, cuid { + @mandatory name : String(111); + dateOfBirth : Date; + dateOfDeath : Date; + placeOfBirth : String; + placeOfDeath : String; + books : Association to many Books + on books.author = $self; +} + +/** Hierarchically organized Code List for Genres */ +entity Genres : CodeList { + key ID : Integer; + parent : Association to Genres; + children : Composition of many Genres + on children.parent = $self; +} diff --git a/sample-app/package-lock.json b/sample-app/package-lock.json new file mode 100644 index 0000000..09cf49d --- /dev/null +++ b/sample-app/package-lock.json @@ -0,0 +1,1878 @@ +{ + "name": "sample-app-cds", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sample-app-cds", + "version": "1.0.0", + "devDependencies": { + "@sap/cds-dk": "^9.7.2" + } + }, + "node_modules/@sap/cds-dk": { + "version": "9.9.2", + "resolved": "https://registry.npmjs.org/@sap/cds-dk/-/cds-dk-9.9.2.tgz", + "integrity": "sha512-cngXMULAuueITOLymme51fBuUKRaqofXDzwyJtnEQ2BLZBz/t6s+rXZV7nY2xJyqU3y0rbB023R9218D25evuw==", + "dev": true, + "hasShrinkwrap": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@cap-js/asyncapi": "^1.0.0", + "@cap-js/openapi": "^1.0.0", + "@sap/cds": "^8.3 || ^9", + "@sap/cds-mtxs": "^2 || ^3", + "@sap/hdi-deploy": "^5", + "express": "^4.22.1 || ^5", + "hdb": "^2.0.0", + "livereload-js": "^4.0.1", + "mustache": "^4.0.1", + "ws": "^8.4.2", + "xml-js": "^1.6.11", + "yaml": "^2" + }, + "bin": { + "cds": "bin/cds.js", + "cds-ts": "bin/cds-ts.js", + "cds-tsx": "bin/cds-tsx.js" + }, + "optionalDependencies": { + "@cap-js/sqlite": ">=1" + } + }, + "node_modules/@sap/cds-dk/node_modules/@cap-js/asyncapi": { + "version": "1.0.3", + "integrity": "sha512-vZSWKAe+3qfvZDXV5SSFiObGWmqyS9MDyEADb5PLVT8kzO39qGaSDPv/GzI/gwvRfCayGAjU4ThiBKrFA7Gclg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "peerDependencies": { + "@sap/cds": ">=7.6" + } + }, + "node_modules/@sap/cds-dk/node_modules/@cap-js/db-service": { + "version": "2.11.0", + "integrity": "sha512-sl33LcxZYAJgMCQZDw4lMGe4kWYq6685Xc6ze4qcoM+rd6aqiyVsSC6C7XH5yerXs7cVHhRC+Dgo8AsaapFzlQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "generic-pool": "^3.9.0" + }, + "peerDependencies": { + "@sap/cds": ">=9.8" + } + }, + "node_modules/@sap/cds-dk/node_modules/@cap-js/openapi": { + "version": "1.4.2", + "integrity": "sha512-Mi02XsSlLy+h9tBLPX1/re2Obsmb1rnIj18AUb1jD5dLvbfedfDojtsuZIQHQImdYNeD6nMgaBQw0g1JRHTdow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "pluralize": "^8.0.0" + }, + "peerDependencies": { + "@sap/cds": ">=7.6" + } + }, + "node_modules/@sap/cds-dk/node_modules/@cap-js/sqlite": { + "version": "2.4.0", + "integrity": "sha512-Ao+AzIN6BWHNpLbGxAzF79OezFNHzDG2srwiBABs0FYxIxEGkc2hg6ETo79pTTt66gcWtx7pWh/N9xk2M6SFBQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@cap-js/db-service": "^2.11.0", + "better-sqlite3": "^12.0.0" + }, + "peerDependencies": { + "@sap/cds": ">=9.8", + "sql.js": "^1.13.0" + }, + "peerDependenciesMeta": { + "sql.js": { + "optional": true + } + } + }, + "node_modules/@sap/cds-dk/node_modules/@eslint/js": { + "version": "10.0.1", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@sap/cds-dk/node_modules/@sap/cds": { + "version": "9.9.1", + "integrity": "sha512-GqdsBsRkZThhpOyzj8ihf/jDmf/2zprZFgaun6ZymUw4/ahzjK/bbdd6eQ8txDuv88pnUl2HPFjvUVq3O/6hCA==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@sap/cds-compiler": "^6.4", + "@sap/cds-fiori": "^2", + "express": "^4.22.1 || ^5", + "yaml": "^2" + }, + "bin": { + "cds-deploy": "bin/deploy.js", + "cds-serve": "bin/serve.js" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@eslint/js": "^9 || ^10", + "tar": "^7.5.6" + }, + "peerDependenciesMeta": { + "tar": { + "optional": true + } + } + }, + "node_modules/@sap/cds-dk/node_modules/@sap/cds-compiler": { + "version": "6.9.2", + "integrity": "sha512-Qv7Zb3RhG92WVm1AjHEJaYbOi3tNT051/EWPYTsYdUe5epYXbR4dJfGpD1eEgo82ThrKCFx0BZfT0b28t0/vqg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "bin": { + "cdsc": "bin/cdsc.js", + "cdshi": "bin/cdshi.js", + "cdsse": "bin/cdsse.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@sap/cds-dk/node_modules/@sap/cds-fiori": { + "version": "2.3.0", + "integrity": "sha512-6oWov+DSpFrSTgxXR0dZhak6aZ/IVRZvaHERMi0EgSTzIJdlvZlpw3Kf18ePMcTrRrtEXwD4RIjKt8pbs0g2Hg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "peerDependencies": { + "@sap/cds": ">=8" + } + }, + "node_modules/@sap/cds-dk/node_modules/@sap/cds-mtxs": { + "version": "3.9.3", + "integrity": "sha512-NdL+ctu1YmxRMFFk8ip3PZHXxIrDv93gK+ZUE+e5B+9MNDvalQcav4QllqwiGJ/CJcfJROWGTt+a554A81dxow==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@sap/hdi-deploy": "^5" + }, + "bin": { + "cds-mtx": "bin/cds-mtx.js", + "cds-mtx-migrate": "bin/cds-mtx-migrate.js" + }, + "peerDependencies": { + "@sap/cds": ">=9" + } + }, + "node_modules/@sap/cds-dk/node_modules/@sap/hdi": { + "version": "4.8.1", + "integrity": "sha512-LfRtIbtfvMl82a8CwpqPJRRB9hRSVUQyvxualYBEzyNu/phuw6bFGQA5LHVngHsms+y797Icm4tl4X+TEkTtlg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "async": "^3.2.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@sap/hana-client": "^2 >= 2.5", + "hdb": "^2 || ^0" + }, + "peerDependenciesMeta": { + "@sap/hana-client": { + "optional": true + }, + "hdb": { + "optional": true + } + } + }, + "node_modules/@sap/cds-dk/node_modules/@sap/hdi-deploy": { + "version": "5.6.1", + "integrity": "sha512-+qQ7qwG8lko303L5yRj2dud/nDAVuVblV/mmzJT44oPbF0Nry18eD2LUS23hFeuxjRa7rYK5YKQ8ffGgWxVrYQ==", + "dev": true, + "license": "See LICENSE file", + "dependencies": { + "@sap/hdi": "^4.8.0", + "@sap/xsenv": "^6.0.0", + "async": "^3.2.6", + "dotenv": "^16.4.5", + "handlebars": "^4.7.8", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=18.x" + }, + "peerDependencies": { + "@sap/hana-client": "^2 >= 2.6", + "hdb": "^2 || ^0" + }, + "peerDependenciesMeta": { + "@sap/hana-client": { + "optional": true + }, + "hdb": { + "optional": true + } + } + }, + "node_modules/@sap/cds-dk/node_modules/@sap/xsenv": { + "version": "6.2.1", + "integrity": "sha512-R1p7VdD3N3jvdkL8av4vLqF+cTQihTz9mCqqF+oa9rVZvgLaCb4ODyZ1dln5/fBgg1OSuch0ESxu3AqZrXVknw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "debug": "4.4.3", + "node-cache": "^5.1.2", + "verror": "1.10.1" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/accepts": { + "version": "2.0.0", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@sap/cds-dk/node_modules/assert-plus": { + "version": "1.0.0", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/@sap/cds-dk/node_modules/async": { + "version": "3.2.6", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sap/cds-dk/node_modules/base64-js": { + "version": "1.5.1", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/@sap/cds-dk/node_modules/better-sqlite3": { + "version": "12.10.0", + "integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" + } + }, + "node_modules/@sap/cds-dk/node_modules/bindings": { + "version": "1.5.0", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/bl": { + "version": "4.1.0", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/body-parser": { + "version": "2.2.2", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sap/cds-dk/node_modules/braces": { + "version": "3.0.3", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sap/cds-dk/node_modules/buffer": { + "version": "5.7.1", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/@sap/cds-dk/node_modules/bytes": { + "version": "3.1.2", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@sap/cds-dk/node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@sap/cds-dk/node_modules/call-bound": { + "version": "1.0.4", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@sap/cds-dk/node_modules/chownr": { + "version": "1.1.4", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/@sap/cds-dk/node_modules/clone": { + "version": "2.1.2", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/@sap/cds-dk/node_modules/content-disposition": { + "version": "1.1.0", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sap/cds-dk/node_modules/content-type": { + "version": "1.0.5", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@sap/cds-dk/node_modules/cookie": { + "version": "0.7.2", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@sap/cds-dk/node_modules/cookie-signature": { + "version": "1.2.2", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/core-util-is": { + "version": "1.0.2", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sap/cds-dk/node_modules/debug": { + "version": "4.4.3", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@sap/cds-dk/node_modules/decompress-response": { + "version": "6.0.0", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sap/cds-dk/node_modules/deep-extend": { + "version": "0.6.0", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/depd": { + "version": "2.0.0", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@sap/cds-dk/node_modules/detect-libc": { + "version": "2.1.2", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sap/cds-dk/node_modules/dotenv": { + "version": "16.6.1", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@sap/cds-dk/node_modules/dunder-proto": { + "version": "1.0.1", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@sap/cds-dk/node_modules/ee-first": { + "version": "1.1.1", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sap/cds-dk/node_modules/encodeurl": { + "version": "2.0.0", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@sap/cds-dk/node_modules/end-of-stream": { + "version": "1.4.5", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/es-define-property": { + "version": "1.0.1", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@sap/cds-dk/node_modules/es-errors": { + "version": "1.3.0", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@sap/cds-dk/node_modules/es-object-atoms": { + "version": "1.1.2", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@sap/cds-dk/node_modules/escape-html": { + "version": "1.0.3", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sap/cds-dk/node_modules/etag": { + "version": "1.8.1", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@sap/cds-dk/node_modules/expand-template": { + "version": "2.0.3", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sap/cds-dk/node_modules/express": { + "version": "5.2.1", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sap/cds-dk/node_modules/extsprintf": { + "version": "1.4.1", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/@sap/cds-dk/node_modules/file-uri-to-path": { + "version": "1.0.0", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@sap/cds-dk/node_modules/fill-range": { + "version": "7.1.1", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sap/cds-dk/node_modules/finalhandler": { + "version": "2.1.1", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sap/cds-dk/node_modules/forwarded": { + "version": "0.2.0", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@sap/cds-dk/node_modules/fresh": { + "version": "2.0.0", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@sap/cds-dk/node_modules/fs-constants": { + "version": "1.0.0", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@sap/cds-dk/node_modules/function-bind": { + "version": "1.1.2", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@sap/cds-dk/node_modules/generic-pool": { + "version": "3.9.0", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@sap/cds-dk/node_modules/get-intrinsic": { + "version": "1.3.0", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@sap/cds-dk/node_modules/get-proto": { + "version": "1.0.1", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@sap/cds-dk/node_modules/github-from-package": { + "version": "0.0.0", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@sap/cds-dk/node_modules/gopd": { + "version": "1.2.0", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@sap/cds-dk/node_modules/handlebars": { + "version": "4.7.9", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/@sap/cds-dk/node_modules/has-symbols": { + "version": "1.1.0", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@sap/cds-dk/node_modules/hasown": { + "version": "2.0.3", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@sap/cds-dk/node_modules/hdb": { + "version": "2.27.1", + "integrity": "sha512-xYL/W+fq2TyGHyzm8muolQnw8tdh4+2NQ8mQP2FpLSuhfJ8l0jQNSUZoAXic7NfMEan1Jvf8V1L4blwkgTc6+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "iconv-lite": "0.7.0" + }, + "engines": { + "node": ">= 18" + }, + "optionalDependencies": { + "lz4-wasm-nodejs": "0.9.2" + } + }, + "node_modules/@sap/cds-dk/node_modules/hdb/node_modules/iconv-lite": { + "version": "0.7.0", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sap/cds-dk/node_modules/http-errors": { + "version": "2.0.1", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sap/cds-dk/node_modules/iconv-lite": { + "version": "0.7.2", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sap/cds-dk/node_modules/ieee754": { + "version": "1.2.1", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@sap/cds-dk/node_modules/inherits": { + "version": "2.0.4", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@sap/cds-dk/node_modules/ini": { + "version": "1.3.8", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/@sap/cds-dk/node_modules/ipaddr.js": { + "version": "1.9.1", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@sap/cds-dk/node_modules/is-number": { + "version": "7.0.0", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/is-promise": { + "version": "4.0.0", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sap/cds-dk/node_modules/livereload-js": { + "version": "4.0.2", + "integrity": "sha512-Fy7VwgQNiOkynYyNBTo3v9hQUhcW5pFAheJN148+DTgpShjsy/22pLHKKwDK5v0kOsZsJBK+6q1PMgLvRmrwFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sap/cds-dk/node_modules/lz4-wasm-nodejs": { + "version": "0.9.2", + "integrity": "sha512-hSwgJPS98q/Oe/89Y1OxzeA/UdnASG8GvldRyKa7aZyoAFCC8VPRtViBSava7wWC66WocjUwBpWau2rEmyFPsw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@sap/cds-dk/node_modules/math-intrinsics": { + "version": "1.1.0", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@sap/cds-dk/node_modules/media-typer": { + "version": "1.1.0", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@sap/cds-dk/node_modules/merge-descriptors": { + "version": "2.0.0", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sap/cds-dk/node_modules/micromatch": { + "version": "4.0.8", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/@sap/cds-dk/node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@sap/cds-dk/node_modules/mime-db": { + "version": "1.54.0", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@sap/cds-dk/node_modules/mime-types": { + "version": "3.0.2", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sap/cds-dk/node_modules/mimic-response": { + "version": "3.1.0", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sap/cds-dk/node_modules/minimist": { + "version": "1.2.8", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@sap/cds-dk/node_modules/mkdirp-classic": { + "version": "0.5.3", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@sap/cds-dk/node_modules/ms": { + "version": "2.1.3", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sap/cds-dk/node_modules/mustache": { + "version": "4.2.0", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/@sap/cds-dk/node_modules/napi-build-utils": { + "version": "2.0.0", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@sap/cds-dk/node_modules/negotiator": { + "version": "1.0.0", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@sap/cds-dk/node_modules/neo-async": { + "version": "2.6.2", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sap/cds-dk/node_modules/node-abi": { + "version": "3.92.0", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@sap/cds-dk/node_modules/node-cache": { + "version": "5.1.2", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/object-inspect": { + "version": "1.13.4", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@sap/cds-dk/node_modules/on-finished": { + "version": "2.4.1", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@sap/cds-dk/node_modules/once": { + "version": "1.4.0", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/@sap/cds-dk/node_modules/parseurl": { + "version": "1.3.3", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@sap/cds-dk/node_modules/path-to-regexp": { + "version": "8.4.2", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sap/cds-dk/node_modules/pluralize": { + "version": "8.0.0", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@sap/cds-dk/node_modules/prebuild-install": { + "version": "7.1.3", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@sap/cds-dk/node_modules/proxy-addr": { + "version": "2.0.7", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@sap/cds-dk/node_modules/pump": { + "version": "3.0.4", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/@sap/cds-dk/node_modules/qs": { + "version": "6.15.2", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@sap/cds-dk/node_modules/range-parser": { + "version": "1.2.1", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@sap/cds-dk/node_modules/raw-body": { + "version": "3.0.2", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@sap/cds-dk/node_modules/rc": { + "version": "1.2.8", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/@sap/cds-dk/node_modules/readable-stream": { + "version": "3.6.2", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@sap/cds-dk/node_modules/router": { + "version": "2.2.0", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@sap/cds-dk/node_modules/safe-buffer": { + "version": "5.2.1", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/@sap/cds-dk/node_modules/safer-buffer": { + "version": "2.1.2", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sap/cds-dk/node_modules/sax": { + "version": "1.6.0", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/semver": { + "version": "7.8.1", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@sap/cds-dk/node_modules/send": { + "version": "1.2.1", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sap/cds-dk/node_modules/serve-static": { + "version": "2.2.1", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sap/cds-dk/node_modules/setprototypeof": { + "version": "1.2.0", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/@sap/cds-dk/node_modules/side-channel": { + "version": "1.1.0", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@sap/cds-dk/node_modules/side-channel-list": { + "version": "1.0.1", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@sap/cds-dk/node_modules/side-channel-map": { + "version": "1.0.1", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@sap/cds-dk/node_modules/side-channel-weakmap": { + "version": "1.0.2", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@sap/cds-dk/node_modules/simple-concat": { + "version": "1.0.1", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/@sap/cds-dk/node_modules/simple-get": { + "version": "4.0.1", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/source-map": { + "version": "0.6.1", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/statuses": { + "version": "2.0.2", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@sap/cds-dk/node_modules/string_decoder": { + "version": "1.3.0", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/strip-json-comments": { + "version": "2.0.1", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/tar-fs": { + "version": "2.1.4", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/@sap/cds-dk/node_modules/tar-stream": { + "version": "2.2.0", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sap/cds-dk/node_modules/to-regex-range": { + "version": "5.0.1", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/toidentifier": { + "version": "1.0.1", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/@sap/cds-dk/node_modules/tunnel-agent": { + "version": "0.6.0", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@sap/cds-dk/node_modules/type-is": { + "version": "2.1.0", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sap/cds-dk/node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@sap/cds-dk/node_modules/uglify-js": { + "version": "3.19.3", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/unpipe": { + "version": "1.0.0", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@sap/cds-dk/node_modules/util-deprecate": { + "version": "1.0.2", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@sap/cds-dk/node_modules/vary": { + "version": "1.1.2", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@sap/cds-dk/node_modules/verror": { + "version": "1.10.1", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/@sap/cds-dk/node_modules/wordwrap": { + "version": "1.0.0", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sap/cds-dk/node_modules/wrappy": { + "version": "1.0.2", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@sap/cds-dk/node_modules/ws": { + "version": "8.21.0", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@sap/cds-dk/node_modules/xml-js": { + "version": "1.6.11", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, + "node_modules/@sap/cds-dk/node_modules/yaml": { + "version": "2.9.0", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/sample-app/package.json b/sample-app/package.json new file mode 100644 index 0000000..fe8f8dd --- /dev/null +++ b/sample-app/package.json @@ -0,0 +1,7 @@ +{ + "name": "sample-app-cds", + "version": "1.0.0", + "devDependencies": { + "@sap/cds-dk": "^9.7.2" + } +} diff --git a/sample-app/pom.xml b/sample-app/pom.xml new file mode 100644 index 0000000..fbfa554 --- /dev/null +++ b/sample-app/pom.xml @@ -0,0 +1,146 @@ + + + 4.0.0 + + customer + sample-app-parent + ${revision} + pom + + sample-app parent + + + + 1.0.0-SNAPSHOT + + + 21 + 4.9.0 + 3.5.6 + + https://nodejs.org/dist/ + UTF-8 + + + + srv + + + + + + + com.sap.cds + cds-services-bom + ${cds.services.version} + pom + import + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + + + + com.sap.cds + cds-maven-plugin + ${cds.services.version} + + + + + + + + maven-compiler-plugin + 3.15.0 + + ${jdk.version} + UTF-8 + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + true + + + + + + maven-surefire-plugin + 3.5.5 + + + + + org.codehaus.mojo + flatten-maven-plugin + 1.7.3 + + true + resolveCiFriendliesOnly + + + + flatten + process-resources + + flatten + + + + flatten.clean + clean + + clean + + + + + + + + maven-enforcer-plugin + 3.6.2 + + + Project Structure Checks + + enforce + + + + + 3.6.3 + + + ${jdk.version} + + + + true + + + + + + + diff --git a/sample-app/srv/_i18n/i18n.properties b/sample-app/srv/_i18n/i18n.properties new file mode 100644 index 0000000..c212370 --- /dev/null +++ b/sample-app/srv/_i18n/i18n.properties @@ -0,0 +1,18 @@ +# Book Order Notification Type (Default - English) +BOOK_ORDERED_DESCRIPTION=New Book Order +BOOK_ORDERED_TEMPLATE_SENSITIVE=New order for "{{bookTitle}}" placed +BOOK_ORDERED_TEMPLATE_PUBLIC=New book order +BOOK_ORDERED_SUBTITLE=Book: {{bookTitle}} — Quantity: {{quantity}} +BOOK_ORDERED_TEMPLATE_GROUPED={{count}} new book orders +BOOK_ORDERED_EMAIL_SUBJECT=New Book Order Confirmation + +# Email Template Texts (Default - English) +BOOK_ORDERED_EMAIL_TITLE=Book Order Confirmation +BOOK_ORDERED_EMAIL_HEADER=Your Order Has Been Placed +BOOK_ORDERED_EMAIL_GREETING=Dear {{buyer}} +BOOK_ORDERED_EMAIL_BODY_LINE1=Thank you for your order! +BOOK_ORDERED_EMAIL_BODY_LINE2=You have successfully ordered +BOOK_ORDERED_EMAIL_BODY_LINE3=copies of +BOOK_ORDERED_EMAIL_BUTTON=View Order +BOOK_ORDERED_EMAIL_SIGNATURE=Happy reading, +BOOK_ORDERED_EMAIL_FOOTER=© Bookshop. All rights reserved. diff --git a/sample-app/srv/_i18n/i18n_de.properties b/sample-app/srv/_i18n/i18n_de.properties new file mode 100644 index 0000000..d5815ad --- /dev/null +++ b/sample-app/srv/_i18n/i18n_de.properties @@ -0,0 +1,18 @@ +# Book Order Notification Type (German) +BOOK_ORDERED_DESCRIPTION=Neue Buchbestellung +BOOK_ORDERED_TEMPLATE_SENSITIVE=Neue Bestellung für "{{bookTitle}}" aufgegeben +BOOK_ORDERED_TEMPLATE_PUBLIC=Neue Buchbestellung +BOOK_ORDERED_SUBTITLE=Buch: {{bookTitle}} — Anzahl: {{quantity}} +BOOK_ORDERED_TEMPLATE_GROUPED={{count}} neue Buchbestellungen +BOOK_ORDERED_EMAIL_SUBJECT=Bestätigung Ihrer Buchbestellung + +# Email Template Texts (German) +BOOK_ORDERED_EMAIL_TITLE=Buchbestellung Bestätigung +BOOK_ORDERED_EMAIL_HEADER=Ihre Bestellung wurde aufgegeben +BOOK_ORDERED_EMAIL_GREETING=Sehr geehrte(r) {{buyer}} +BOOK_ORDERED_EMAIL_BODY_LINE1=Vielen Dank für Ihre Bestellung! +BOOK_ORDERED_EMAIL_BODY_LINE2=Sie haben erfolgreich +BOOK_ORDERED_EMAIL_BODY_LINE3=Exemplar(e) von +BOOK_ORDERED_EMAIL_BUTTON=Bestellung ansehen +BOOK_ORDERED_EMAIL_SIGNATURE=Viel Spaß beim Lesen, +BOOK_ORDERED_EMAIL_FOOTER=© Buchhandlung. Alle Rechte vorbehalten. diff --git a/sample-app/srv/admin-service.cds b/sample-app/srv/admin-service.cds new file mode 100644 index 0000000..225cb8a --- /dev/null +++ b/sample-app/srv/admin-service.cds @@ -0,0 +1,35 @@ +using {sap.capire.bookshop as my} from '../db/schema'; + +service AdminService @(requires: 'admin') { + @notifications: [ + { + type : 'LowStockAlert', + on : ['UPDATE'], + recipients: 'admin@example.com', + where : ($self.stock < 10), + parameters: { + bookTitle: $self.title, + stock : $self.stock, + } + }, + { + type : 'StockReplenished', + on : ['restock'], + recipients: 'admin@example.com', + parameters: { + bookTitle: $self.title, + newStock : $self.stock, + } + } + ] + entity Books as projection on my.Books + actions { + @Core.OperationAvailable: true + @Common.SideEffects: { + TargetProperties: ['_it/stock'], + TargetEntities : ['_it'] + } + action restock(amount : Integer) returns Books; + }; + entity Authors as projection on my.Authors; +} diff --git a/sample-app/srv/cat-service.cds b/sample-app/srv/cat-service.cds new file mode 100644 index 0000000..e321bf6 --- /dev/null +++ b/sample-app/srv/cat-service.cds @@ -0,0 +1,31 @@ +using {sap.capire.bookshop as my} from '../db/schema'; + +service CatalogService { + + /** For displaying lists of Books */ + @readonly + entity ListOfBooks as + projection on Books + excluding { + descr + }; + + entity Books as + projection on my.Books { + *, + author.name as author + } + excluding { + createdBy, + modifiedBy + } + actions { + action submitOrder(quantity : Integer) returns Books; + }; + + event OrderedBook : { + book : Books:ID; + quantity : Integer; + buyer : String + }; +} diff --git a/sample-app/srv/notifications.cds b/sample-app/srv/notifications.cds new file mode 100644 index 0000000..f7881ef --- /dev/null +++ b/sample-app/srv/notifications.cds @@ -0,0 +1,89 @@ +using from 'com.sap.cds/cds-feature-notifications'; + +namespace sap.capire.bookshop.notifications; + +service NotificationService { + + /** + * Example 1: Manual notification with i18n, HTML email, delivery channels, + * semantic object navigation, and customizable template. + */ + @description : '{i18n>BOOK_ORDERED_DESCRIPTION}' + @notification: { + customizable: true, + template: { + title : '{i18n>BOOK_ORDERED_TEMPLATE_SENSITIVE}', + publicTitle : '{i18n>BOOK_ORDERED_TEMPLATE_PUBLIC}', + subtitle : '{i18n>BOOK_ORDERED_SUBTITLE}', + groupedTitle : '{i18n>BOOK_ORDERED_TEMPLATE_GROUPED}', + email: { + subject: '{i18n>BOOK_ORDERED_EMAIL_SUBJECT}', + html : 'email-templates/book-ordered.html', + }, + }, + deliveryChannels: [ + { + channel : #Mail, + enabled : true, + defaultPreference : true, + }, + { + channel : #Web, + enabled : true, + defaultPreference : true, + } + ], + priority: #HIGH, + } + @Common.SemanticObject : 'Books' + @Common.SemanticObjectAction: 'display' + event BookOrdered { + recipients : String; + bookTitle : String; + quantity : Integer; + buyer : String; + } + + /** + * Example 2: Low stock alert with dynamic priority using contains() function. + * Demonstrates array of recipients — multiple emails/UUIDs in a single notification. + * The plugin auto-detects whether each value is an email or a UUID. + */ + @notification: { + template: { + title : 'Low stock alert for "{{bookTitle}}"', + publicTitle : 'Low stock alert', + subtitle : 'Remaining stock: {{stock}}', + groupedTitle : '{{count}} low stock alerts', + }, + deliveryChannels: [ + { channel: #Mail, enabled: true, defaultPreference: true } + ], + priority: (contains(bookTitle, 'Heights') ? 'HIGH' : (stock < 5 ? 'HIGH' : 'MEDIUM')), + } + event LowStockAlert { + recipients : array of String; // Case 2: multiple recipients (emails or UUIDs) + bookTitle : String; + stock : Integer; + } + + /** + * Example 3: Stock replenished notification, triggered via bound action. + * See admin-service.cds — demonstrates @notifications annotation with + * bound action trigger (restock). + */ + @notification: { + template: { + title : 'Stock replenished for "{{bookTitle}}"', + publicTitle : 'Stock replenished', + subtitle : 'New stock level: {{newStock}}', + groupedTitle : '{{count}} stock replenishments', + }, + priority: #MEDIUM, + } + event StockReplenished { + recipients : String; + bookTitle : String; + newStock : Integer; + } +} diff --git a/sample-app/srv/pom.xml b/sample-app/srv/pom.xml new file mode 100644 index 0000000..99d3192 --- /dev/null +++ b/sample-app/srv/pom.xml @@ -0,0 +1,159 @@ + + 4.0.0 + + + sample-app-parent + customer + ${revision} + + + sample-app + jar + + sample-app + + + + + + com.sap.cds + cds-starter-spring-boot + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.sap.cds + cds-adapter-odata-v4 + runtime + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-starter-security + + + + + com.sap.cds + cds-feature-notifications + 1.0.0-SNAPSHOT + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + false + + + + repackage + + repackage + + + exec + + + + + + + + com.sap.cds + cds-maven-plugin + + + cds.clean + + clean + + + + + cds.install-node + + install-node + + + + + cds.npm-ci + + npm + + + ci + + + + + cds.resolve + + resolve + + + + + cds.build + + cds + + + + build --for java + deploy --to h2 --with-mocks --dry --out "${project.basedir}/src/main/resources/schema-h2.sql" + + + + + + cds.generate + + generate + + + cds.gen + true + true + true + true + + NotificationProviderService.** + NotificationProviderService + NotificationTypeProviderService.** + NotificationTypeProviderService + NotificationTemplateProviderService.** + NotificationTemplateProviderService + + + + + + + + + \ No newline at end of file diff --git a/sample-app/srv/src/main/java/customer/sample_app/Application.java b/sample-app/srv/src/main/java/customer/sample_app/Application.java new file mode 100644 index 0000000..4fe540a --- /dev/null +++ b/sample-app/srv/src/main/java/customer/sample_app/Application.java @@ -0,0 +1,13 @@ +package customer.sample_app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/sample-app/srv/src/main/java/customer/sample_app/config/DestinationConfiguration.java b/sample-app/srv/src/main/java/customer/sample_app/config/DestinationConfiguration.java new file mode 100644 index 0000000..a8943f3 --- /dev/null +++ b/sample-app/srv/src/main/java/customer/sample_app/config/DestinationConfiguration.java @@ -0,0 +1,47 @@ +/* + * © 2026 SAP SE or an SAP affiliate company and cds-feature-notifications contributors. + */ +package customer.sample_app.config; +/* +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import com.sap.cds.services.application.ApplicationLifecycleService; +import com.sap.cds.services.handler.EventHandler; +import com.sap.cds.services.handler.annotations.Before; +import com.sap.cds.services.handler.annotations.ServiceName; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultDestinationLoader; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor; +import com.sap.cloud.sdk.cloudplatform.connectivity.OAuth2DestinationBuilder; +import com.sap.cloud.sdk.cloudplatform.connectivity.OnBehalfOf; +import com.sap.cloud.security.config.ClientCredentials; +import com.sap.cloud.security.config.ClientIdentity; + +@Component +@Profile("!cloud") +@ServiceName(ApplicationLifecycleService.DEFAULT_NAME) +public class DestinationConfiguration implements EventHandler { + + @Before(event = ApplicationLifecycleService.EVENT_APPLICATION_PREPARED) + public void registerDestination() { + final String clientId = ""; + final String clientSecret = ""; + final String tokenUrl = ""; + final String host = ""; + final String destinationName = "SAP_Notifications"; + + ClientIdentity clientCredentials = new ClientCredentials(clientId, clientSecret); + + DefaultHttpDestination httpDestination = OAuth2DestinationBuilder + .forTargetUrl(host) + .withTokenEndpoint(tokenUrl) + .withClient(clientCredentials, OnBehalfOf.TECHNICAL_USER_CURRENT_TENANT) + .name(destinationName) + .build(); + + DestinationAccessor.prependDestinationLoader( + new DefaultDestinationLoader().registerDestination(httpDestination)); + } +} +*/ diff --git a/sample-app/srv/src/main/java/customer/sample_app/handlers/AdminServiceHandler.java b/sample-app/srv/src/main/java/customer/sample_app/handlers/AdminServiceHandler.java new file mode 100644 index 0000000..7f4d185 --- /dev/null +++ b/sample-app/srv/src/main/java/customer/sample_app/handlers/AdminServiceHandler.java @@ -0,0 +1,41 @@ +package customer.sample_app.handlers; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.sap.cds.ql.Select; +import com.sap.cds.ql.Update; +import com.sap.cds.services.handler.EventHandler; +import com.sap.cds.services.handler.annotations.On; +import com.sap.cds.services.handler.annotations.ServiceName; +import com.sap.cds.services.persistence.PersistenceService; + +import cds.gen.adminservice.AdminService_; +import cds.gen.adminservice.Books; +import cds.gen.adminservice.Books_; +import cds.gen.adminservice.BooksRestockContext; + +@Component +@ServiceName(AdminService_.CDS_NAME) +public class AdminServiceHandler implements EventHandler { + + @Autowired + private PersistenceService db; + + @On(event = "restock", entity = Books_.CDS_NAME) + public void onRestock(BooksRestockContext context) { + Books currentBook = db.run(context.getCqn()).single(Books.class); + String bookId = currentBook.getId(); + + db.run(Update.entity("sap.capire.bookshop.Books") + .data(Map.of("stock", context.getAmount())) + .where(b -> b.get("ID").eq(bookId))); + + Books result = db.run(Select.from("sap.capire.bookshop.Books") + .where(b -> b.get("ID").eq(bookId))).single(Books.class); + context.setResult(result); + context.setCompleted(); + } +} diff --git a/sample-app/srv/src/main/java/customer/sample_app/handlers/CatalogServiceHandler.java b/sample-app/srv/src/main/java/customer/sample_app/handlers/CatalogServiceHandler.java new file mode 100644 index 0000000..c2fbcda --- /dev/null +++ b/sample-app/srv/src/main/java/customer/sample_app/handlers/CatalogServiceHandler.java @@ -0,0 +1,82 @@ +package customer.sample_app.handlers; + +import static cds.gen.catalogservice.CatalogService_.BOOKS; + +import java.util.stream.Stream; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.sap.cds.ql.Select; +import com.sap.cds.ql.Update; +import com.sap.cds.services.cds.CqnService; +import com.sap.cds.services.handler.EventHandler; +import com.sap.cds.services.handler.annotations.After; +import com.sap.cds.services.handler.annotations.On; +import com.sap.cds.services.handler.annotations.ServiceName; +import com.sap.cds.services.persistence.PersistenceService; + +import cds.gen.catalogservice.Books; +import cds.gen.catalogservice.Books_; +import cds.gen.catalogservice.BooksSubmitOrderContext; +import cds.gen.catalogservice.CatalogService_; +import cds.gen.catalogservice.OrderedBook; +import cds.gen.catalogservice.OrderedBookContext; +import cds.gen.sap.capire.bookshop.notifications.notificationservice.BookOrdered; +import cds.gen.sap.capire.bookshop.notifications.notificationservice.BookOrderedContext; +import cds.gen.sap.capire.bookshop.notifications.notificationservice.NotificationService; + +@Component +@ServiceName(CatalogService_.CDS_NAME) +public class CatalogServiceHandler implements EventHandler { + + @Autowired + private PersistenceService db; + + @Autowired + private NotificationService.Application notificationService; + + @On(entity = Books_.CDS_NAME) + public Books submitOrder(BooksSubmitOrderContext context) { + // get book from bound action context + Books book = db.run(context.getCqn()).single(Books.class); + String bookId = book.getId(); + + // decrease and update stock in database + db.run(Update.entity(BOOKS).byId(bookId).set(b -> b.stock(), s -> s.minus(context.getQuantity()))); + + // read updated stock from database + book = db.run(Select.from(BOOKS).where(b -> b.ID().eq(bookId))).single(); + + // publish CDS event + OrderedBook orderedBook = OrderedBook.create(); + orderedBook.setBook(book.getId()); + orderedBook.setQuantity(context.getQuantity()); + orderedBook.setBuyer(context.getUserInfo().getName()); + + OrderedBookContext orderedBookEvent = OrderedBookContext.create(); + orderedBookEvent.setData(orderedBook); + context.getService().emit(orderedBookEvent); + + // Example 1: send manual notification via NotificationService + BookOrdered notification = BookOrdered.create(); + notification.setRecipients(context.getUserInfo().getName()); + notification.setBookTitle(book.getTitle()); + notification.setQuantity(context.getQuantity()); + notification.setBuyer(context.getUserInfo().getName()); + + BookOrderedContext notifContext = BookOrderedContext.create(); + notifContext.setData(notification); + notificationService.emit(notifContext); + + return book; + } + + @After(event = CqnService.EVENT_READ) + public void discountBooks(Stream books) { + books.filter(b -> b.getTitle() != null && b.getStock() != null) + .filter(b -> b.getStock() > 200) + .forEach(b -> b.setTitle(b.getTitle() + " (discounted)")); + } + +} diff --git a/sample-app/srv/src/main/resources/application.yaml b/sample-app/srv/src/main/resources/application.yaml new file mode 100644 index 0000000..284749d --- /dev/null +++ b/sample-app/srv/src/main/resources/application.yaml @@ -0,0 +1,59 @@ +--- +management: + endpoint: + health: + show-components: always + probes: + enabled: true + endpoints: + web: + exposure: + include: health + health: + defaults: + enabled: false + ping: + enabled: true + db: + enabled: true +logging: + level: + '[com.sap.cds.services.impl.runtime.CdsRuntimeImpl]': DEBUG + '[com.sap.cloud.sdk.datamodel.odata]': DEBUG + '[com.sap.cds.services.impl.odata]': DEBUG +--- +spring: + config.activate.on-profile: default + sql.init.platform: h2 +cds: + index-page: + enabled: true + security: + mock: + enabled: true + users: + - name: admin@example.com + password: admin + roles: + - admin + - name: user@example.com + password: user + environment: + production: + enabled: false +--- +spring: + config.activate.on-profile: test +cds: + security: + mock: + users: + - name: admin + password: admin + roles: + - admin + data-source.auto-config.enabled: false + index-page.enabled: true + environment: + production: + enabled: false diff --git a/sample-app/srv/src/main/resources/email-templates/book-ordered.html b/sample-app/srv/src/main/resources/email-templates/book-ordered.html new file mode 100644 index 0000000..64a1bdd --- /dev/null +++ b/sample-app/srv/src/main/resources/email-templates/book-ordered.html @@ -0,0 +1,38 @@ + + + + {i18n>BOOK_ORDERED_EMAIL_TITLE} + + + + + + + + + + + + +
+

{i18n>BOOK_ORDERED_EMAIL_HEADER}

+
+

{i18n>BOOK_ORDERED_EMAIL_GREETING},

+

{i18n>BOOK_ORDERED_EMAIL_BODY_LINE1}

+

+ {i18n>BOOK_ORDERED_EMAIL_BODY_LINE2} + {{quantity}} {i18n>BOOK_ORDERED_EMAIL_BODY_LINE3} + {{bookTitle}}. +

+

+ + {i18n>BOOK_ORDERED_EMAIL_BUTTON} + +

+

{i18n>BOOK_ORDERED_EMAIL_SIGNATURE}
Bookshop

+
+ {i18n>BOOK_ORDERED_EMAIL_FOOTER} +
+ + diff --git a/sample-app/srv/src/test/java/customer/sample_app/handlers/CatalogServiceHandlerTest.java b/sample-app/srv/src/test/java/customer/sample_app/handlers/CatalogServiceHandlerTest.java new file mode 100644 index 0000000..f73c4da --- /dev/null +++ b/sample-app/srv/src/test/java/customer/sample_app/handlers/CatalogServiceHandlerTest.java @@ -0,0 +1,42 @@ +package customer.sample_app.handlers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import cds.gen.catalogservice.Books; + +class CatalogServiceHandlerTest { + + private CatalogServiceHandler handler = new CatalogServiceHandler(); + private Books book = Books.create(); + + @BeforeEach + public void prepareBook() { + book.setTitle("title"); + } + + @Test + void testDiscount() { + book.setStock(500); + handler.discountBooks(Stream.of(book)); + assertEquals("title (discounted)", book.getTitle()); + } + + @Test + void testNoDiscount() { + book.setStock(100); + handler.discountBooks(Stream.of(book)); + assertEquals("title", book.getTitle()); + } + + @Test + void testNoStockAvailable() { + handler.discountBooks(Stream.of(book)); + assertEquals("title", book.getTitle()); + } + +} From 24c41f3825a8f92778e6b3604c5dd7b8dc7d9eb5 Mon Sep 17 00:00:00 2001 From: Buse Halis Date: Wed, 1 Jul 2026 10:43:34 +0200 Subject: [PATCH 4/7] docs: update sample-app README --- sample-app/README.md | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/sample-app/README.md b/sample-app/README.md index dd7e974..1d06481 100644 --- a/sample-app/README.md +++ b/sample-app/README.md @@ -18,8 +18,7 @@ This sample demonstrates how to use the `cds-feature-notifications` plugin in a ## Prerequisites - Java 17 or higher -- Maven 3.6 or higher -- Node.js 18 or higher +- Maven 3.6.3 or higher - npm - CAP Java (`com.sap.cds:cds-services-bom`) **4.9.0 or higher** @@ -47,8 +46,7 @@ This sample demonstrates how to use the `cds-feature-notifications` plugin in a ``` 5. **Access the application**: - - Browse Books: http://localhost:8080/browse/index.html - - Admin Books: http://localhost:8080/admin-books/index.html + - http://localhost:8080/index.html ## Notification Examples @@ -152,7 +150,7 @@ event LowStockAlert { @notifications: [{ type : 'LowStockAlert', on : ['UPDATE'], - recipients: $self.createdBy, + recipients: 'admin@example.com', where : ($self.stock < 10), parameters: { bookTitle: $self.title, @@ -199,7 +197,7 @@ event StockReplenished { @notifications: [{ type : 'StockReplenished', on : ['restock'], - recipients: $self.createdBy, + recipients: 'admin@example.com', parameters: { bookTitle: $self.title, newStock : $self.stock, @@ -297,7 +295,22 @@ No configuration needed. Notifications are logged to the console instead of bein You will see log output like: ``` -INFO - [LocalHandler] Notification sent: BookOrdered → recipient: admin +┌──────────────────────────────────────────────────────────────┐ +│ LOCAL NOTIFICATION (not sent to ANS) +├──────────────────────────────────────────────────────────────┤ +│ From: noreply@notifications.local +│ To: admin@example.com +│ Subject: Order Confirmation: Wuthering Heights +│ Priority: HIGH +├──────────────────────────────────────────────────────────────┤ +│ 1 copy ordered +│ +│ Notification Type: BookOrdered +│ Parameters: +│ - bookTitle = Wuthering Heights +│ - quantity = 1 +│ - buyer = admin +└──────────────────────────────────────────────────────────────┘ ``` ### Production Mode @@ -329,11 +342,6 @@ For local testing against a real ANS instance, use the `DestinationConfiguration - `destinationName` — must be `SAP_Notifications` (matches the plugin's expected destination name) 3. Run with `cds.environment.production.enabled: true` -## Troubleshooting - -- **Port conflicts**: If port 8080 is in use, specify a different port: `mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8081"` -- **Plugin not found**: Make sure you ran `mvn install -pl cds-feature-notifications -DskipTests` from the project root first - ## Advanced Topics For advanced topics like production ANS configuration, recipient formats, dynamic priority expressions, language resolution via IAS destination, template customization, batch notifications, and outbox support, see the [main project documentation](../README.md). From e8582aa4c9a632ef0020615b43e926fb9fd7f50b Mon Sep 17 00:00:00 2001 From: Buse Halis Date: Wed, 1 Jul 2026 14:51:35 +0200 Subject: [PATCH 5/7] chore: change mock user credentials to admin:admin in integration-tests --- .../srv/src/main/resources/application.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-tests/srv/src/main/resources/application.yaml b/integration-tests/srv/src/main/resources/application.yaml index 62ba5cb..112856c 100644 --- a/integration-tests/srv/src/main/resources/application.yaml +++ b/integration-tests/srv/src/main/resources/application.yaml @@ -29,14 +29,14 @@ cds: security: mock: users: - - name: test.user@example.com - password: myPass + - name: admin + password: admin roles: - cds.Developer index-page.enabled: true environment: production: - enabled: true + enabled: false --- spring: config.activate.on-profile: test @@ -44,8 +44,8 @@ cds: security: mock: users: - - name: test.user@example.com - password: myPass + - name: admin + password: admin roles: - cds.Developer data-source.auto-config.enabled: false From 8b2c2b2faff74f1000a31006313568b1d8f23568 Mon Sep 17 00:00:00 2001 From: Buse Halis Date: Wed, 1 Jul 2026 14:56:13 +0200 Subject: [PATCH 6/7] fix: use admin@example.com in test profile to ensure valid recipient format for entity notification tests --- integration-tests/srv/src/main/resources/application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/srv/src/main/resources/application.yaml b/integration-tests/srv/src/main/resources/application.yaml index 112856c..14decf8 100644 --- a/integration-tests/srv/src/main/resources/application.yaml +++ b/integration-tests/srv/src/main/resources/application.yaml @@ -44,7 +44,7 @@ cds: security: mock: users: - - name: admin + - name: admin@example.com password: admin roles: - cds.Developer From fd27f719b016eee56d8e8e0d2e8fffe74643b8c6 Mon Sep 17 00:00:00 2001 From: Buse Halis Date: Wed, 1 Jul 2026 15:13:11 +0200 Subject: [PATCH 7/7] fix: restore test.user@example.com in test profile to match @WithMockUser annotation in entity notification tests --- integration-tests/srv/src/main/resources/application.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/srv/src/main/resources/application.yaml b/integration-tests/srv/src/main/resources/application.yaml index 14decf8..12a5f79 100644 --- a/integration-tests/srv/src/main/resources/application.yaml +++ b/integration-tests/srv/src/main/resources/application.yaml @@ -44,8 +44,8 @@ cds: security: mock: users: - - name: admin@example.com - password: admin + - name: test.user@example.com + password: test roles: - cds.Developer data-source.auto-config.enabled: false