Compare commits
167 Commits
eslint-fla
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4db3a7aba2 | ||
|
|
78cea03ae7 | ||
|
|
ef8702acf8 | ||
|
|
dcc74fbd10 | ||
|
|
d1353db705 | ||
|
|
c10ca17546 | ||
|
|
dcb0c0394a | ||
|
|
c7d869ef62 | ||
|
|
991b672e53 | ||
|
|
a7b4a23163 | ||
|
|
36d58664c9 | ||
|
|
f8252207a0 | ||
|
|
600afb704a | ||
|
|
6fa2aaee34 | ||
|
|
987611b009 | ||
|
|
ce1da7fce9 | ||
|
|
395587b1bf | ||
|
|
bab7827000 | ||
|
|
efc2f3632f | ||
|
|
61ceb4a63a | ||
|
|
226ab2539e | ||
|
|
56a9796329 | ||
|
|
ece1962052 | ||
|
|
f90c43150a | ||
|
|
7d170eed73 | ||
|
|
2b11b8b7e6 | ||
|
|
b888a65381 | ||
|
|
75eec39a46 | ||
|
|
339c87c55a | ||
|
|
c81fd6e31e | ||
|
|
5263dbbb38 | ||
|
|
a79e07deed | ||
|
|
62070bb851 | ||
|
|
1ff53edf85 | ||
|
|
cc1e8ffd54 | ||
|
|
38decb41dc | ||
|
|
9451e3ff67 | ||
|
|
335d6bb53b | ||
|
|
31fbad4442 | ||
|
|
4232ab1d3a | ||
|
|
81557d5ad7 | ||
|
|
1c8b71fe1c | ||
|
|
396fd050ef | ||
|
|
9b5aa61a1f | ||
|
|
f21fbae587 | ||
|
|
919b811235 | ||
|
|
4de60d6115 | ||
|
|
f0527e142f | ||
|
|
f65c71e930 | ||
|
|
45995bcfc5 | ||
|
|
ef6c76f2d1 | ||
|
|
62e9043119 | ||
|
|
bfa20d0b49 | ||
|
|
bfa1c174f1 | ||
|
|
fdb613a023 | ||
|
|
4070c98f52 | ||
|
|
8f036f9bee | ||
|
|
7cb886b7e2 | ||
|
|
5bf70eb589 | ||
|
|
e55856d259 | ||
|
|
e983dbe56f | ||
|
|
6b5bbe8626 | ||
|
|
05fe6c4c17 | ||
|
|
ebb2c2d064 | ||
|
|
87c13e9654 | ||
|
|
59a3a639c4 | ||
|
|
817be8854a | ||
|
|
29382a18e9 | ||
|
|
6512367a2c | ||
|
|
0af0192818 | ||
|
|
23922c7f99 | ||
|
|
ef91e5e526 | ||
|
|
8ab5430a14 | ||
|
|
6c0520d83f | ||
|
|
04eeab9217 | ||
|
|
7b92463394 | ||
|
|
737e766d53 | ||
|
|
ac4154eef3 | ||
|
|
49a4dfc988 | ||
|
|
af3e905866 | ||
|
|
aee2f35327 | ||
|
|
bbaaefae5b | ||
|
|
c1dbe94c64 | ||
|
|
5e161d1564 | ||
|
|
7536a30313 | ||
|
|
14ac3efaa1 | ||
|
|
9c476eb348 | ||
|
|
23bea0f629 | ||
|
|
8c6ba30673 | ||
|
|
06956b1077 | ||
|
|
d1a4a65eae | ||
|
|
e0b9a29bbc | ||
|
|
32bc0ab94f | ||
|
|
b652150788 | ||
|
|
4379170c89 | ||
|
|
29f9016c37 | ||
|
|
b1a13f2460 | ||
|
|
ee028e76b6 | ||
|
|
33b7691f51 | ||
|
|
66f55959ea | ||
|
|
5e5ed7749d | ||
|
|
543405e6bb | ||
|
|
396ddab332 | ||
|
|
7c4478b385 | ||
|
|
1cc507a484 | ||
|
|
c0358bce4d | ||
|
|
010521b120 | ||
|
|
48e45729ff | ||
|
|
d124a903cc | ||
|
|
8587be71c2 | ||
|
|
eb9a5ed971 | ||
|
|
2f91f7d2d4 | ||
|
|
23cc7794b3 | ||
|
|
26d1b1a655 | ||
|
|
a8d6fc43b5 | ||
|
|
0ba1f2e294 | ||
|
|
53fbbaa3da | ||
|
|
d844642ce9 | ||
|
|
8343013d3a | ||
|
|
9db71a6635 | ||
|
|
a9de08222c | ||
|
|
daabec1ce9 | ||
|
|
cf7506f2f7 | ||
|
|
e6358b55d1 | ||
|
|
e7927b39b1 | ||
|
|
7b7686cb29 | ||
|
|
e2902640e3 | ||
|
|
ccb14d9791 | ||
|
|
3f3af6c39c | ||
|
|
c8aba771c5 | ||
|
|
a90175f28d | ||
|
|
f41416408a | ||
|
|
7a2e345cd5 | ||
|
|
722cf622eb | ||
|
|
b8d389a40a | ||
|
|
82c633ce01 | ||
|
|
3161758e5f | ||
|
|
02ea798f5f | ||
|
|
09f02676ef | ||
|
|
cae0d36206 | ||
|
|
ea79264e9d | ||
|
|
c2d1217e4a | ||
|
|
d739680180 | ||
|
|
326729e03d | ||
|
|
6e56eb9c01 | ||
|
|
1a0d3e60cf | ||
|
|
402a5816e3 | ||
|
|
cdb9ae6436 | ||
|
|
4ba262f02a | ||
|
|
187014d98b | ||
|
|
85f24970de | ||
|
|
a191c4fd50 | ||
|
|
ac3cfa81c7 | ||
|
|
710dd7ac32 | ||
|
|
bbe2bae087 | ||
|
|
ec8608faab | ||
|
|
4c46f115f5 | ||
|
|
c6b88f6b66 | ||
|
|
aaa94e2d5f | ||
|
|
0f6809a45b | ||
|
|
6249fa2270 | ||
|
|
9e02e2c796 | ||
|
|
d9f50d635f | ||
|
|
546f374b08 | ||
|
|
bb127bcc58 | ||
|
|
d08749fd21 | ||
|
|
be5696b14e |
2425
.deploy/app-router/package-lock.json
generated
Normal file
2425
.deploy/app-router/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
.deploy/app-router/package.json
Normal file
9
.deploy/app-router/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "approuter",
|
||||
"dependencies": {
|
||||
"@sap/approuter": "^20.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node node_modules/@sap/approuter/approuter.js"
|
||||
}
|
||||
}
|
||||
1
.deploy/app-router/resources/bookshop
Symbolic link
1
.deploy/app-router/resources/bookshop
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../bookshop/app/vue
|
||||
1
.deploy/app-router/resources/orders
Symbolic link
1
.deploy/app-router/resources/orders
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../orders/app/orders
|
||||
1
.deploy/app-router/resources/reviews
Symbolic link
1
.deploy/app-router/resources/reviews
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../reviews/app/vue
|
||||
41
.deploy/app-router/xs-app.json
Normal file
41
.deploy/app-router/xs-app.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"welcomeFile": "app/bookshop/index.html",
|
||||
"routes": [
|
||||
{
|
||||
"source": "^/app/(.*)$",
|
||||
"target": "$1",
|
||||
"localDir": "resources",
|
||||
"cacheControl": "no-cache, no-store, must-revalidate"
|
||||
},
|
||||
{
|
||||
"source": "^/admin/(.*)$",
|
||||
"target": "/admin/$1",
|
||||
"destination": "bookstore-api",
|
||||
"csrfProtection": true
|
||||
},
|
||||
{
|
||||
"source": "^/browse/(.*)$",
|
||||
"target": "/browse/$1",
|
||||
"destination": "bookstore-api",
|
||||
"csrfProtection": true
|
||||
},
|
||||
{
|
||||
"source": "^/user/(.*)$",
|
||||
"target": "/user/$1",
|
||||
"destination": "bookstore-api",
|
||||
"csrfProtection": true
|
||||
},
|
||||
{
|
||||
"source": "^/odata/v4/orders/(.*)$",
|
||||
"target": "/odata/v4/orders/$1",
|
||||
"destination": "orders-api",
|
||||
"csrfProtection": true
|
||||
},
|
||||
{
|
||||
"source": "^/reviews/(.*)$",
|
||||
"target": "/reviews/$1",
|
||||
"destination": "reviews-api",
|
||||
"csrfProtection": true
|
||||
}
|
||||
]
|
||||
}
|
||||
31
.eslintrc
31
.eslintrc
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"plugin:@sap/cds/recommended",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2022": true,
|
||||
"node": true,
|
||||
"jest": true,
|
||||
"mocha": true
|
||||
},
|
||||
"globals": {
|
||||
"SELECT": true,
|
||||
"INSERT": true,
|
||||
"UPSERT": true,
|
||||
"UPDATE": true,
|
||||
"DELETE": true,
|
||||
"CREATE": true,
|
||||
"DROP": true,
|
||||
"CDL": true,
|
||||
"CQL": true,
|
||||
"cds": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"require-atomic-updates": "off",
|
||||
"require-await":"warn",
|
||||
"no-unused-vars": ["warn", { "argsIgnorePattern": "_" }]
|
||||
}
|
||||
}
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -15,3 +15,9 @@ updates:
|
||||
- dependency-name: "chai"
|
||||
# chai 5 doesn't work atm w/ cds.test, TODO fix that in cds.test
|
||||
versions: ["5.x"]
|
||||
- dependency-name: "chai-as-promised"
|
||||
# chai-as-promised 8 doesn't work atm w/ cds.test, TODO fix that in cds.test
|
||||
versions: ["8.x"]
|
||||
- dependency-name: "express"
|
||||
# express 5 not supported atm
|
||||
versions: ["5.x"]
|
||||
|
||||
18
.github/workflows/node.js.yml
vendored
18
.github/workflows/node.js.yml
vendored
@@ -11,18 +11,28 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x, 18.x]
|
||||
node-version: [22.x, 20.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22.x
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
|
||||
3
.npmrc
3
.npmrc
@@ -1,3 +0,0 @@
|
||||
# Ensure we always use public packages, i.e. avoid using local registries from ~/.npmrc
|
||||
@sap:registry=https://registry.npmjs.org/
|
||||
registry=https://registry.npmjs.org/
|
||||
@@ -24,6 +24,6 @@ Disclaimer: The code in this project may include calls to APIs (“API Calls”)
|
||||
you any rights to use or access any SAP External Product, or provide any third
|
||||
parties the right to use of access any SAP External Product, through API Calls.
|
||||
|
||||
Files: *
|
||||
Copyright: 2019-2020 SAP SE or an SAP affiliate company and cap-cloud-samples
|
||||
Files: *.*
|
||||
Copyright: 2019-2025 SAP SE or an SAP affiliate company and cap-cloud-samples
|
||||
License: Apache-2.0
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
{
|
||||
"$schema": "https://aka.ms/codetour-schema",
|
||||
"title": "Database Functions",
|
||||
"steps": [
|
||||
{
|
||||
"title": "Introduction",
|
||||
"description": "### Database Functions in CDS Models\n\nIn this tour, you'll learn how to add database-specific functions to CDS models in your application."
|
||||
},
|
||||
{
|
||||
"file": "bookshop/db/schema.cds",
|
||||
"description": "#### Basic Schema\n\nWe want to add two fields to the `Authors` entity, one for the author's age and one for the span of years that she or he lived.\n\nThese two fields can be computed out of the existing `dateOfBirth` and `dateOfDeath` fields.",
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 19,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 21,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"title": "Base fields in Author"
|
||||
},
|
||||
{
|
||||
"file": "bookshop/srv/admin-service.cds",
|
||||
"description": "This is how the `Authors` entity gets exposed in an OData or REST service.\n\nIn the next step, you'll see how we extend this projection.",
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 5,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"title": "Authors service"
|
||||
},
|
||||
{
|
||||
"file": "fiori/db/sqlite/index.cds",
|
||||
"description": "#### SQLite Implementation\n\nHere's the first implementation for SQLite. It computes the two fields `age` and `lifetime` through SQLite's [strftime](https://sqlite.org/lang_datefunc.html) function.\n\nThrough the [`extend projection`](https://cap.cloud.sap/docs/cds/cdl#extend-view) clause you can add additional fields to projection entities. These are deployed as database views, which is why we can integrate the database functions in the first place.\n",
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 7,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 11,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"title": "SQLite implementation"
|
||||
},
|
||||
{
|
||||
"file": "fiori/db/hana/index.cds",
|
||||
"description": "#### SAP HANA Implementation\n\nThis is the second implementation for SAP HANA. It computes the same two fields `age` and `lifetime` through the [YEARS_BETWEEN](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/7c0d2c161ea34def86de3f5eadd6a0af.html) and [YEAR](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/20f5fac6751910148dabd3c6821f907d.html) functions of SAP HANA.\n\n#### File Layout and Code Structure\n\nNote the path of the `.cds` file we are in: it's in a subfolder of `db`, so that it's _not_ automatically picked up when we start the application. The same is true for the SQLite implementation: it's in a separate `db/sqlite/` folder as well. In the next step, you'll see how these files are loaded.\n\nAlso, we choose to implement all of that as an extension of the original bookshop here in the _fiori_ package. See the first [CAP Samples] code tour for more details on the different packages of this repository.",
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 7,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 11,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"title": "SAP HANA implementation"
|
||||
},
|
||||
{
|
||||
"file": "fiori/package.json",
|
||||
"description": "#### Configuration\n\nThe `cds.requires` section in `package.json` is a place to configure which of the `db/sqlite` and `db/hana` folders are used for which database.\n\nWe use [Node.js profiles](https://cap.cloud.sap/docs/node.js/cds-env#profiles) to separate the configuration.\nIn the `development` profile, you can see that `db/sqlite` is set as the model, while the `db/hana` folder is configured in the `production` profile. `db-ext` is a pseudo datasource, its name doesn't matter.\n\nSee [`cds.resolve`](https://cap.cloud.sap/docs/node.js/cds-compile#cds-resolve) to learn more about how models are found.",
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 41,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 48,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"title": "Configuration"
|
||||
},
|
||||
{
|
||||
"file": "fiori/package.json",
|
||||
"description": "#### Run with SQLite\n\nTo run with `development` and an in-memory SQLite database, you don't need to do anything special, because it's activated by default. Just run:\n\n>> cds watch fiori\n\nThen open [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) to see the two new fields.\n",
|
||||
"line": 43,
|
||||
"title": "Run with SQLite"
|
||||
},
|
||||
{
|
||||
"file": "fiori/package.json",
|
||||
"description": "#### Deploy the CDS Model to SAP HANA\n\nTo 'activate' SAP HANA through the `production` profile, you can use the global `--production` flag:\n\n>> cd fiori; cds deploy --to hana --production\n\n[Learn more about SAP HANA deployment](https://cap.cloud.sap/docs/guides/databases#get-hana)\n\n#### Run the Application\n\n>> cd fiori; cds watch --production\n\nThe service on [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) is the same as before, but this time the `Authors` entity is backed by a database view with an SAP HANA function.\n\n#### More\n\nIf you don't see data, you can add some in the next step.",
|
||||
"line": 46,
|
||||
"title": "Run with SAP HANA"
|
||||
},
|
||||
{
|
||||
"file": "fiori/test/requests.http",
|
||||
"description": "### Add More Data\n\nOptionally you can add some `Authors` data by clicking on the _Send Request_ link (provided by the [REST client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension).",
|
||||
"line": 72,
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 67,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 73,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"title": "Add Data"
|
||||
},
|
||||
{
|
||||
"title": "Wrap-up",
|
||||
"description": "### Summary\n\nThat's it! You have seen: \n- How to integrate database-specific functions in a CDS model.\n- How to switch between the two implementations for SQLite and SAP HANA."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
{
|
||||
"$schema": "https://aka.ms/codetour-schema",
|
||||
"title": "CAP Samples",
|
||||
"steps": [
|
||||
{
|
||||
"title": "Welcome",
|
||||
"file": "README.md",
|
||||
"description": "### Welcome to CAP Samples!\n\nThis tour leads you through a collection of samples for the [SAP Cloud Application Programming Model (CAP)](https://cap.cloud.sap).\nYou will learn which features of the programming model are demonstrated in which sample.\n\nLet's start!",
|
||||
"line": 2,
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 3,
|
||||
"character": 108
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "hello/srv/world.cds",
|
||||
"description": "### Hello World!\n\nThis is a simplistic [Hello World](https://cap.cloud.sap/docs/get-started/hello-world) service using [CDS](https://cap.cloud.sap/docs/cds/) and [cds.services](https://cap.cloud.sap/docs/node.js/api#services-api).",
|
||||
"line": 2,
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"title": "Hello World!"
|
||||
},
|
||||
{
|
||||
"file": "bookshop/db/schema.cds",
|
||||
"description": "### A Bookshop!\n\nIntroduces:\n- [Project Setup](https://cap.cloud.sap/docs/get-started/) and [Layouts](https://cap.cloud.sap/docs/get-started/projects)\n- [Domain Modeling](https://cap.cloud.sap/docs/guides/domain-models)\n- [Defining Services](https://cap.cloud.sap/docs/guides/providing-services)\n- [Generic Providers](https://cap.cloud.sap/docs/guides/generic-providers)\n- [Adding Custom Logic](https://cap.cloud.sap/docs/guides/service-impl)\n- [Using Databases](https://cap.cloud.sap/docs/guides/databases)\n",
|
||||
"line": 1,
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 32,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"title": "Bookshop"
|
||||
},
|
||||
{
|
||||
"file": "common/index.cds",
|
||||
"description": "### Extend and Reuse\n\nShowcases how to extend [@sap/cds/common](https://cap.cloud.sap/docs/cds/common) thereby covering:\n- Building [extension packages](https://cap.cloud.sap/docs/guides/domain-models#aspects-extensibility)\n- Providing [reuse packages](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content)\n- [Verticalization](https://cap.cloud.sap/docs/cds/common#adapting-to-your-needs)\n- Using [Aspects](https://cap.cloud.sap/docs/cds/cdl#aspects)\n- Used in the [fiori app sample](#fiori)\n",
|
||||
"line": 1,
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 46,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"title": "Common"
|
||||
},
|
||||
{
|
||||
"file": "orders/db/schema.cds",
|
||||
"description": "### Orders - Compositions and Serving Documents\n\nA standalone orders management service, demonstrating:\n- Using [Compositions](https://cap.cloud.sap/docs/cds/cdl#compositions) in [Domain Models](https://cap.cloud.sap/docs/guides/domain-models), along with\n- [Serving deeply nested documents](https://cap.cloud.sap/docs/guides/generic-providers#serving-structured-data)\n",
|
||||
"line": 1,
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 27,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"title": "Orders"
|
||||
},
|
||||
{
|
||||
"file": "reviews/db/schema.cds",
|
||||
"description": "### Reviews - More Modularity\n\nShows how to implement a modular service to manage product reviews, including:\n- Consuming other services synchronously and asynchronously\n- Serving requests synchronously\n- Emitting events asynchronously\n- Grow as you go, with:\n- Mocking app services\n- Running service meshes\n- Late-cut Micro Services\n- As well as managed data, input validations, and authorization\n",
|
||||
"line": 1,
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 39,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"title": "Reviews"
|
||||
},
|
||||
{
|
||||
"title": "Bookstore",
|
||||
"description": "### Bookstore - Reuse and UI\n\n- A [composite app, reusing and combining](https://cap.cloud.sap/docs/guides/reuse-and-compose) these packages:\n - [@capire/bookshop](bookshop)\n - [@capire/reviews](reviews)\n - [@capire/orders](orders)\n - [@capire/common](common)\n- [The Vue.js app](bookshop/app/vue) imported from bookshop is served as well\n- [The Vue.js app](reviews/app/vue) imported from reviews is served as well\n- [The Fiori app](orders/app) imported from orders is served as well\n- [OpenAPI export + Swagger UI](https://cap.cloud.sap/docs/advanced/openapi)"
|
||||
},
|
||||
{
|
||||
"file": "fiori/app/services.cds",
|
||||
"description": "### Annotations for SAP Fiori Elements\n\nAdds an SAP Fiori elements application to bookstore, thereby introducing:\n- OData Annotations in `.cds` files\n- Support for Fiori Draft\n- Support for Value Helps\n- Serving SAP Fiori apps locally\n\nSee the [Serving Fiori UIs](https://cap.cloud.sap/docs/advanced/fiori) documentation for more information.",
|
||||
"line": 1,
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 13,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"title": "Fiori"
|
||||
},
|
||||
{
|
||||
"file": "package.json",
|
||||
"description": "### All-in-one Monorepo\n\nEach sample sub directory essentially is a standard npm package, some with standard npm dependencies to other samples. The root folder's [package.json](package.json) has local links to the sub folders, such that an `npm install` populates a local `node_modules` folder acts like a local npm registry to the individual sample packages.\n",
|
||||
"selection": {
|
||||
"start": {
|
||||
"line": 8,
|
||||
"character": 1
|
||||
},
|
||||
"end": {
|
||||
"line": 16,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"title": "Packages"
|
||||
}
|
||||
],
|
||||
"isPrimary": true,
|
||||
"description": "Overview of CAP Samples for Node.js"
|
||||
}
|
||||
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@@ -9,8 +9,6 @@
|
||||
"dbaeumer.vscode-eslint",
|
||||
"mechatroner.rainbow-csv",
|
||||
"humao.rest-client",
|
||||
"sdras.night-owl",
|
||||
"vsls-contrib.codetour"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": [
|
||||
|
||||
27
.vscode/launch.json
vendored
27
.vscode/launch.json
vendored
@@ -18,8 +18,8 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Fiori App",
|
||||
"command": "npx cds watch fiori",
|
||||
"name": "bookstore",
|
||||
"command": "npx cds watch bookstore",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"skipFiles": [
|
||||
@@ -29,29 +29,6 @@
|
||||
"**/cds/lib/req/cds-context.js",
|
||||
"**/odata-v4/okra/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Debug Mocha Tests",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"port": 9229,
|
||||
"continueOnAttach": true,
|
||||
"skipFiles": [
|
||||
"<node_internals>/**",
|
||||
"**/node_modules/**",
|
||||
"**/cds/lib/lazy.js",
|
||||
"**/cds/lib/req/cds-context.js",
|
||||
"**/odata-v4/okra/**",
|
||||
]
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "pickString",
|
||||
"id": "sample",
|
||||
"description": "Which sample do you want to start?",
|
||||
"options": [ "bookshop", "fiori", "reviews", "reviews" ],
|
||||
"default": "bookshop"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
17
.vscode/settings.json
vendored
17
.vscode/settings.json
vendored
@@ -1,9 +1,8 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
".reuse/**": true,
|
||||
"**/.gitignore": true,
|
||||
"**/.vscode": true,
|
||||
"LICENSES/**": true
|
||||
"**/node_modules": true,
|
||||
"LICENSES": true,
|
||||
".reuse": true
|
||||
},
|
||||
"debug.javascript.terminalOptions": {
|
||||
"skipFiles": [
|
||||
@@ -13,13 +12,5 @@
|
||||
"**/cds/lib/req/cds-context.js",
|
||||
"**/odata-v4/okra/**"
|
||||
]
|
||||
},
|
||||
"eslint.probe": [
|
||||
"cds",
|
||||
"csn",
|
||||
"csv",
|
||||
"csv (semicolon)",
|
||||
"tsv",
|
||||
"tab"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
75
README.md
75
README.md
@@ -1,75 +0,0 @@
|
||||
# Welcome to cap/samples
|
||||
|
||||
Find here a collection of samples for the [SAP Cloud Application Programming Model](https://cap.cloud.sap) organized in a simplistic [monorepo setup](samples.md#all-in-one-monorepo).
|
||||
|
||||
[See **Overview** of contained samples](samples.md):
|
||||
|
||||

|
||||
|
||||

|
||||
[](https://api.reuse.software/info/github.com/SAP-samples/cloud-cap-samples)
|
||||
|
||||
|
||||
|
||||
### Preliminaries
|
||||
|
||||
1. Ensure you have the latest LTS version of Node.js installed (see [Getting Started](https://cap.cloud.sap/docs/get-started/))
|
||||
2. Install [**@sap/cds-dk**](https://cap.cloud.sap/docs/get-started/) globally:
|
||||
|
||||
```sh
|
||||
npm i -g @sap/cds-dk
|
||||
```
|
||||
|
||||
3. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/tools#vscode)
|
||||
|
||||
### Download
|
||||
|
||||
If you've [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/main.zip).
|
||||
|
||||
```sh
|
||||
git clone https://github.com/sap-samples/cloud-cap-samples samples
|
||||
cd samples
|
||||
```
|
||||
|
||||
### Setup
|
||||
|
||||
In the samples folder run:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
With that you're ready to run the samples, for example:
|
||||
|
||||
```sh
|
||||
cds watch bookshop
|
||||
```
|
||||
|
||||
After that open this link in your browser: [http://localhost:4004](http://localhost:4004)
|
||||
|
||||
When asked to log in, type `alice` as user and leave the password field blank, which is the [default user](https://cap.cloud.sap/docs/node.js/authentication#mocked).
|
||||
|
||||
### Testing
|
||||
|
||||
Run the provided tests with [_jest_](http://jestjs.io) or [_mocha_](http://mochajs.org), for example:
|
||||
|
||||
```sh
|
||||
npx jest
|
||||
```
|
||||
> While mocha is a bit smaller and faster, jest runs tests in parallel and isolation, which allows to run all tests.
|
||||
|
||||
## Code Tours
|
||||
|
||||
Take one of the [guided tours](.tours) in VS Code through our CAP samples and learn which CAP features are showcased by the different parts of the repository. Just install the [CodeTour extension](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour) for VS Code. We'll add more code tours in the future. Stay tuned!
|
||||
|
||||
## Get Support
|
||||
|
||||
Check out the documentation at [https://cap.cloud.sap](https://cap.cloud.sap). <br>
|
||||
In case you've a question, find a bug, or otherwise need support, use our [community](https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce) to get more visibility.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2022 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE) file.
|
||||
@@ -19,7 +19,7 @@ const books = Vue.createApp ({
|
||||
search: ({target:{value:v}}) => books.fetch(v && '&$search='+v),
|
||||
|
||||
async fetch (etc='') {
|
||||
const {data} = await GET(`/ListOfBooks?$expand=genre,currency${etc}`)
|
||||
const {data} = await GET(`/ListOfBooks?$expand=genre($select=name),currency($select=symbol)${etc}`)
|
||||
books.list = data.value
|
||||
},
|
||||
|
||||
@@ -77,7 +77,7 @@ function csrfToken (request) {
|
||||
document.csrfToken = token
|
||||
request.headers['x-csrf-token'] = document.csrfToken
|
||||
return request
|
||||
}).catch(_ => {
|
||||
}).catch(() => {
|
||||
document.csrfToken = null // set mark to not try again
|
||||
return request
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<head>
|
||||
<title> Capire Books </title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/primitive-ui/dist/css/main.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
|
||||
<style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
ID,title,descr,author_ID,stock,price,currency_code,genre_ID
|
||||
201,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.",101,12,11.11,GBP,11
|
||||
207,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.",107,11,12.34,GBP,11
|
||||
251,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.",150,333,13.13,USD,16
|
||||
252,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.",150,555,14,USD,15
|
||||
271,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.",170,22,150,JPY,13
|
||||
201,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.",101,12,11.11,GBP,11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
||||
207,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.",107,11,12.34,GBP,11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
||||
251,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.",150,333,13.13,USD,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
||||
252,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.",150,555,14,USD,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
||||
271,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.",170,22,150,JPY,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
ID,locale,title,descr
|
||||
201,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."
|
||||
201,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."
|
||||
207,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"
|
||||
252,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."
|
||||
ID_texts,ID,locale,title,descr
|
||||
d2a65a27-9f2a-480f-bc38-84ee8ec5c13e,201,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."
|
||||
8c42c706-a979-41cf-9ffe-91e6cf1383a0,201,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."
|
||||
9e1c4c81-dc90-4600-85b1-e9dd4bf12ce0,207,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"
|
||||
9be0524b-4cb9-4fc1-9dc2-d65b1c13cf53,252,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."
|
||||
|
||||
|
@@ -1,16 +1,43 @@
|
||||
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
|
||||
10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,,Fiction
|
||||
11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Drama
|
||||
12aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Poetry
|
||||
13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Fantasy
|
||||
131aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Fairy Tale
|
||||
132aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Epic Fantasy
|
||||
133aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,High Fantasy
|
||||
134aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Gothic
|
||||
14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Science Fiction
|
||||
141aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Utopian and Dystopian
|
||||
1411aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,141aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Utopian
|
||||
1412aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,141aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Dystopian
|
||||
14121aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1412aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Cyberpunk
|
||||
141211aa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14121aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Steampunk
|
||||
142aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Space Opera
|
||||
143aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Time Travel
|
||||
144aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Tech Noir
|
||||
15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Romance
|
||||
151aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Contemporary Romance
|
||||
152aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Historical Romance
|
||||
153aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Romantic Suspense
|
||||
16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Mystery
|
||||
161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Crime
|
||||
1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Thriller
|
||||
16111aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Police Procedural
|
||||
16112aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Legal Thriller
|
||||
16113aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Medical Thriller
|
||||
16114aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Spy Thriller
|
||||
1612aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Detective
|
||||
1613aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Suspense
|
||||
162aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Noir
|
||||
1621aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,162aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Nordic Noir
|
||||
1622aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,162aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Tart Noir
|
||||
163aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Cozy Mystery
|
||||
17aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Adventure
|
||||
18aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Short Story
|
||||
19aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Graphic Novel
|
||||
20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,,Non-Fiction
|
||||
21aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Biography
|
||||
22aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,21aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Autobiography
|
||||
23aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Essay
|
||||
24aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Speech
|
||||
|
||||
|
@@ -1,3 +1,5 @@
|
||||
const cds = require('@sap/cds')
|
||||
|
||||
/**
|
||||
* In order to keep basic bookshop sample as simple as possible, we don't add
|
||||
* reuse dependencies. This db/init.js ensures we still have a minimum set of
|
||||
@@ -6,7 +8,7 @@
|
||||
|
||||
// NOTE: We use cds.on('served') to delay the UPSERTs after the db init
|
||||
// to run after all INSERTs from .csv files happened.
|
||||
module.exports = cds.on('served', ()=> cds.run(
|
||||
module.exports = cds.on('served', ()=>
|
||||
UPSERT.into ('sap.common.Currencies') .columns (
|
||||
[ 'code', 'symbol', 'name' ]
|
||||
) .rows (
|
||||
@@ -16,4 +18,4 @@ module.exports = cds.on('served', ()=> cds.run(
|
||||
[ 'ILS', '₪', 'Shekel' ],
|
||||
[ 'JPY', '¥', 'Yen' ],
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ entity Books : managed {
|
||||
author : Association to Authors @mandatory;
|
||||
genre : Association to Genres;
|
||||
stock : Integer;
|
||||
price : Decimal;
|
||||
price : Price;
|
||||
currency : Currency;
|
||||
image : LargeBinary @Core.MediaType: 'image/png';
|
||||
}
|
||||
@@ -25,7 +25,14 @@ entity Authors : managed {
|
||||
|
||||
/** Hierarchically organized Code List for Genres */
|
||||
entity Genres : sap.common.CodeList {
|
||||
key ID : Integer;
|
||||
key ID : UUID;
|
||||
parent : Association to Genres;
|
||||
children : Composition of many Genres on children.parent = $self;
|
||||
}
|
||||
|
||||
type Price : Decimal(9,2);
|
||||
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// temporary workaround for reuse in fiori sample and hana deployment
|
||||
annotate Books with @fiori.draft.enabled;
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
const { CatalogService } = require('./srv/cat-service')
|
||||
module.exports = { CatalogService }
|
||||
@@ -6,14 +6,13 @@
|
||||
"app",
|
||||
"srv",
|
||||
"db",
|
||||
"index.cds",
|
||||
"index.js"
|
||||
"index.cds"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@cap-js/sqlite": "*"
|
||||
"@cap-js/sqlite": ">=1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sap/cds": "^7",
|
||||
"@sap/cds": ">=7",
|
||||
"express": "^4.17.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
2
bookshop/srv/access-control.cds
Normal file
2
bookshop/srv/access-control.cds
Normal file
@@ -0,0 +1,2 @@
|
||||
using { AdminService } from './admin-service';
|
||||
annotate AdminService with @requires:'admin';
|
||||
@@ -1,5 +1,6 @@
|
||||
using { sap.capire.bookshop as my } from '../db/schema';
|
||||
service AdminService @(requires:'admin', path:'/admin') {
|
||||
entity Books as projection on my.Books;
|
||||
service AdminService @(path:'/admin') {
|
||||
entity Authors as projection on my.Authors;
|
||||
entity Books as projection on my.Books;
|
||||
entity Genres as projection on my.Genres;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
const cds = require('@sap/cds/lib')
|
||||
const cds = require('@sap/cds')
|
||||
|
||||
module.exports = class AdminService extends cds.ApplicationService { init(){
|
||||
this.before ('NEW','Authors', genid)
|
||||
this.before ('NEW','Books', genid)
|
||||
this.before (['NEW','CREATE'],'Authors', genid)
|
||||
this.before (['NEW','CREATE'],'Books', genid)
|
||||
return super.init()
|
||||
}}
|
||||
|
||||
/** Generate primary keys for target entity in request */
|
||||
async function genid (req) {
|
||||
if (req.data.ID) return
|
||||
const {id} = await SELECT.one.from(req.target).columns('max(ID) as id')
|
||||
req.data.ID = id - id % 100 + 100 + 1
|
||||
req.data.ID = id + 4 // Note: that is not safe! ok for this sample only.
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
module.exports = class CatalogService extends cds.ApplicationService { init() {
|
||||
const cds = require('@sap/cds')
|
||||
|
||||
class CatalogService extends cds.ApplicationService { init() {
|
||||
|
||||
const { Books } = cds.entities('sap.capire.bookshop')
|
||||
const { ListOfBooks } = this.entities
|
||||
@@ -32,3 +34,5 @@ module.exports = class CatalogService extends cds.ApplicationService { init() {
|
||||
// Delegate requests to the underlying generic service
|
||||
return super.init()
|
||||
}}
|
||||
|
||||
module.exports = CatalogService
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
const cds = require('@sap/cds')
|
||||
const { expect } = cds.test
|
||||
|
||||
describe('cds.ql → cqn', () => {
|
||||
|
||||
const cds = require('@sap/cds/lib')
|
||||
const { expect } = cds.test
|
||||
const Foo = { name: 'Foo' }
|
||||
const Books = { name: 'capire.bookshop.Books' }
|
||||
|
||||
@@ -676,7 +677,7 @@ describe('cds.ql → cqn', () => {
|
||||
.to.eql(INSERT.into(Foo).entries(...entries))
|
||||
.to.eql(INSERT.into(Foo).entries(entries))
|
||||
.to.eql({
|
||||
INSERT: { into: cds.env.ql.quirks_mode ? 'Foo' : { ref: ['Foo'] }, entries },
|
||||
INSERT: { into: { ref: ['Foo'] }, entries },
|
||||
})
|
||||
})
|
||||
|
||||
@@ -692,7 +693,7 @@ describe('cds.ql → cqn', () => {
|
||||
.to.eql(INSERT.into(Foo).columns('a', 'b').rows([1, 2], [3, 4]))
|
||||
.to.eql({
|
||||
INSERT: {
|
||||
into: cds.env.ql.quirks_mode ? 'Foo' : { ref: ['Foo'] },
|
||||
into: { ref: ['Foo'] },
|
||||
columns: ['a', 'b'],
|
||||
rows: [
|
||||
[1, 2],
|
||||
@@ -706,7 +707,7 @@ describe('cds.ql → cqn', () => {
|
||||
expect(INSERT.into(Foo).columns('a', 'b').values([1, 2]))
|
||||
.to.eql(INSERT.into(Foo).columns('a', 'b').values(1, 2))
|
||||
.to.eql({
|
||||
INSERT: { into: cds.env.ql.quirks_mode ? 'Foo' : { ref: ['Foo'] }, columns: ['a', 'b'], values: [1, 2] },
|
||||
INSERT: { into: { ref: ['Foo'] }, columns: ['a', 'b'], values: [1, 2] },
|
||||
})
|
||||
})
|
||||
|
||||
@@ -721,7 +722,7 @@ describe('cds.ql → cqn', () => {
|
||||
test('entity (..., <key>)', () => {
|
||||
const cqnWhere = {
|
||||
UPDATE: {
|
||||
entity: cds.env.ql.quirks_mode ? 'capire.bookshop.Books' : { ref: ['capire.bookshop.Books'] },
|
||||
entity: { ref: ['capire.bookshop.Books'] },
|
||||
where: [{ ref: ['ID'] }, '=', { val: 4711 }],
|
||||
},
|
||||
}
|
||||
@@ -765,7 +766,7 @@ describe('cds.ql → cqn', () => {
|
||||
.to.eql(UPDATE(Foo).with({ foo: 11, bar: { '-=': 22 } }))
|
||||
.to.eql({
|
||||
UPDATE: {
|
||||
entity: cds.env.ql.quirks_mode ? 'Foo' : { ref: ['Foo'] },
|
||||
entity: { ref: ['Foo'] },
|
||||
data: { foo: 11 },
|
||||
with: {
|
||||
bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] },
|
||||
@@ -776,7 +777,7 @@ describe('cds.ql → cqn', () => {
|
||||
// some more
|
||||
expect(UPDATE(Foo).with(`bar = coalesce(x,y), car = 'foo''s bar, car'`)).to.eql({
|
||||
UPDATE: {
|
||||
entity: cds.env.ql.quirks_mode ? 'Foo' : { ref: ['Foo'] },
|
||||
entity: { ref: ['Foo'] },
|
||||
data: {
|
||||
car: "foo's bar, car",
|
||||
},
|
||||
@@ -796,7 +797,7 @@ describe('cds.ql → cqn', () => {
|
||||
test('from (..., <key>)', () => {
|
||||
const cqnWhere = {
|
||||
DELETE: {
|
||||
from: cds.env.ql.quirks_mode ? 'capire.bookshop.Books' : { ref: ['capire.bookshop.Books'] },
|
||||
from: { ref: ['capire.bookshop.Books'] },
|
||||
where: [{ ref: ['ID'] }, '=', { val: 4711 }],
|
||||
},
|
||||
}
|
||||
53
bookshop/test/consuming-actions.test.js
Normal file
53
bookshop/test/consuming-actions.test.js
Normal file
@@ -0,0 +1,53 @@
|
||||
const cds = require("@sap/cds");
|
||||
const { expect } = cds.test(
|
||||
"serve",
|
||||
"CatalogService",
|
||||
"--from",
|
||||
"@capire/bookshop,@capire/common",
|
||||
"--in-memory"
|
||||
);
|
||||
|
||||
describe("Consuming actions locally", () => {
|
||||
let cats, CatalogService, Books, stockBefore;
|
||||
const BOOK_ID = 251;
|
||||
const QUANTITY = 1;
|
||||
|
||||
before("bootstrap the database", async () => {
|
||||
CatalogService = cds.services.CatalogService;
|
||||
expect(CatalogService).not.to.be.undefined;
|
||||
|
||||
Books = CatalogService.entities.Books;
|
||||
expect(Books).not.to.be.undefined;
|
||||
|
||||
cats = await cds.connect.to("CatalogService");
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
// Read the stock before the action is called
|
||||
stockBefore = (await cats.get(Books, BOOK_ID)).stock;
|
||||
});
|
||||
|
||||
it("calls unbound actions - basic variant using srv.send", async () => {
|
||||
// Use a managed transaction to create a continuation with an authenticated user
|
||||
const res1 = await cats.tx({ user: "alice" }, () => {
|
||||
return cats.send("submitOrder", { book: BOOK_ID, quantity: QUANTITY });
|
||||
});
|
||||
expect(res1.stock).to.eql(stockBefore - QUANTITY);
|
||||
});
|
||||
|
||||
it("calls unbound actions - named args variant", async () => {
|
||||
// Use a managed transaction to create a continuation with an authenticated user
|
||||
const res2 = await cats.tx({ user: "alice" }, () => {
|
||||
return cats.submitOrder({ book: BOOK_ID, quantity: QUANTITY });
|
||||
});
|
||||
expect(res2.stock).to.eql(stockBefore - QUANTITY);
|
||||
});
|
||||
|
||||
it("calls unbound actions - positional args variant", async () => {
|
||||
// Use a managed transaction to create a continuation with an authenticated user
|
||||
const res3 = await cats.tx({ user: "alice" }, () => {
|
||||
return cats.submitOrder(BOOK_ID, QUANTITY);
|
||||
});
|
||||
expect(res3.stock).to.eql(stockBefore - QUANTITY);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
const cds = require('@sap/cds/lib')
|
||||
const cds = require('@sap/cds')
|
||||
const { expect } = cds.test ('@capire/bookshop')
|
||||
cds.User.default = cds.User.privileged // disable auth checks
|
||||
|
||||
describe('cap/samples - Consuming Services locally', () => {
|
||||
|
||||
const { expect } = cds.test ('@capire/bookshop')
|
||||
|
||||
it('bootstrapped the database successfully', ()=>{
|
||||
const { AdminService } = cds.services
|
||||
const { Authors } = AdminService.entities
|
||||
@@ -33,33 +33,11 @@ describe('cap/samples - Consuming Services locally', () => {
|
||||
})
|
||||
})
|
||||
}).where(`name like`, 'E%')
|
||||
if (require('semver').gte(cds.version, '5.9.0')) {
|
||||
expect(authors).to.containSubset([
|
||||
{
|
||||
name: 'Emily Brontë',
|
||||
books: [
|
||||
{
|
||||
title: 'Wuthering Heights',
|
||||
currency: { name: 'British Pound', symbol: '£' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Edgar Allen Poe',
|
||||
books: [
|
||||
{ title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } },
|
||||
{ title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } },
|
||||
],
|
||||
},
|
||||
])
|
||||
return
|
||||
}
|
||||
expect(authors).to.containSubset([
|
||||
{
|
||||
name: 'Emily Brontë',
|
||||
books: [
|
||||
{
|
||||
ID: 201,
|
||||
title: 'Wuthering Heights',
|
||||
currency: { name: 'British Pound', symbol: '£' },
|
||||
},
|
||||
@@ -68,8 +46,8 @@ describe('cap/samples - Consuming Services locally', () => {
|
||||
{
|
||||
name: 'Edgar Allen Poe',
|
||||
books: [
|
||||
{ ID: 251, title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } },
|
||||
{ ID: 252, title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } },
|
||||
{ title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } },
|
||||
{ title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } },
|
||||
],
|
||||
},
|
||||
])
|
||||
15
bookshop/test/custom-handlers.test.js
Normal file
15
bookshop/test/custom-handlers.test.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const cds = require('@sap/cds')
|
||||
const { GET, POST, expect } = cds.test(__dirname+'/..')
|
||||
cds.User.default = cds.User.Privileged // hard core monkey patch
|
||||
|
||||
describe('cap/samples - Custom Handlers', () => {
|
||||
|
||||
it('should reject out-of-stock orders', async () => {
|
||||
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled
|
||||
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled
|
||||
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.rejectedWith(
|
||||
/409 - 5 exceeds stock for book #201/)
|
||||
const { data } = await GET`/admin/Books/201/stock/$value`
|
||||
expect(data).to.equal(2)
|
||||
})
|
||||
})
|
||||
@@ -1,38 +0,0 @@
|
||||
#################################################
|
||||
#
|
||||
# Genres
|
||||
#
|
||||
|
||||
GET http://localhost:4004/test/Genres?
|
||||
###
|
||||
|
||||
GET http://localhost:4004/test/Genres?
|
||||
&$filter=parent_ID eq null&$select=name
|
||||
&$expand=children($select=name)
|
||||
###
|
||||
|
||||
POST http://localhost:4004/test/Genres?
|
||||
Content-Type: application/json
|
||||
|
||||
{ "ID":100, "name":"Some Sample Genres...", "children":[
|
||||
{ "ID":101, "name":"Cat", "children":[
|
||||
{ "ID":102, "name":"Kitty", "children":[
|
||||
{ "ID":103, "name":"Kitty Cat", "children":[
|
||||
{ "ID":104, "name":"Aristocat" } ]},
|
||||
{ "ID":105, "name":"Kitty Bat" } ]},
|
||||
{ "ID":106, "name":"Catwoman", "children":[
|
||||
{ "ID":107, "name":"Catalina" } ]} ]},
|
||||
{ "ID":108, "name":"Catweazle" }
|
||||
]}
|
||||
###
|
||||
|
||||
GET http://localhost:4004/test/Genres(100)?
|
||||
# &$expand=children
|
||||
# &$expand=children($expand=children($expand=children($expand=children)))
|
||||
###
|
||||
|
||||
DELETE http://localhost:4004/test/Genres(103)
|
||||
###
|
||||
|
||||
DELETE http://localhost:4004/test/Genres(100)
|
||||
###
|
||||
@@ -1,5 +1,5 @@
|
||||
const cds = require('@sap/cds/lib')
|
||||
const { expect } = cds.test.in(__dirname,'..')
|
||||
const cds = require('@sap/cds')
|
||||
const { expect } = cds.test.in(__dirname,'..','..')
|
||||
|
||||
describe('cap/samples - Hierarchical Data', ()=>{
|
||||
|
||||
@@ -45,6 +45,7 @@ describe('cap/samples - Hierarchical Data', ()=>{
|
||||
]
|
||||
}
|
||||
})
|
||||
/* temp skip for release
|
||||
if (q.forSQL) expect (q.forSQL()) .to.eql ({
|
||||
SELECT: {
|
||||
from: { ref:[ "Categories" ], as: "Categories" },
|
||||
@@ -75,51 +76,48 @@ describe('cap/samples - Hierarchical Data', ()=>{
|
||||
`) as children FROM Categories as Categories` +
|
||||
`)`
|
||||
)
|
||||
*/
|
||||
})
|
||||
|
||||
it ('supports nested reads', async()=>{
|
||||
expect (await
|
||||
SELECT.one.from (Cats, c=>{
|
||||
c.ID, c.name.as('parent'), c.children (c=>{
|
||||
c.name.as('child')
|
||||
})
|
||||
}) .where ({name:'Cat'})
|
||||
) .to.eql (
|
||||
{ ID:101, parent:'Cat', children:[
|
||||
{ child:'Kitty' },
|
||||
{ child:'Catwoman' },
|
||||
]}
|
||||
)
|
||||
})
|
||||
it ('supports nested reads', ()=> expect (
|
||||
SELECT.one.from (Cats, c=>{
|
||||
c.ID, c.name.as('parent'), c.children (c=>{
|
||||
c.name.as('child')
|
||||
})
|
||||
}) .where ({name:'Cat'})
|
||||
) .to.eventually.eql (
|
||||
{ ID:101, parent:'Cat', children:[
|
||||
{ child:'Kitty' },
|
||||
{ child:'Catwoman' },
|
||||
]}
|
||||
))
|
||||
|
||||
it ('supports deeply nested reads', async()=>{
|
||||
expect (await SELECT.one.from (Cats, c=>{
|
||||
it ('supports deeply nested reads', ()=> expect (
|
||||
SELECT.one.from (Cats, c=>{
|
||||
c.ID, c.name, c.children (
|
||||
c => { c.name },
|
||||
{levels:3}
|
||||
)
|
||||
}) .where ({name:'Cat'})
|
||||
) .to.eql (
|
||||
{ ID:101, name:'Cat', children:[
|
||||
{ name:'Kitty', children:[
|
||||
{ name:'Kitty Cat', children:[
|
||||
{ name:'Aristocat' }, ]}, // level 3
|
||||
{ name:'Kitty Bat', children:[] }, ]},
|
||||
{ name:'Catwoman', children:[
|
||||
{ name:'Catalina', children:[] } ]},
|
||||
]}
|
||||
)
|
||||
})
|
||||
) .to.eventually.eql (
|
||||
{ ID:101, name:'Cat', children:[
|
||||
{ name:'Kitty', children:[
|
||||
{ name:'Kitty Cat', children:[
|
||||
{ name:'Aristocat' }, ]}, // level 3
|
||||
{ name:'Kitty Bat', children:[] }, ]},
|
||||
{ name:'Catwoman', children:[
|
||||
{ name:'Catalina', children:[] } ]},
|
||||
]}
|
||||
))
|
||||
|
||||
it ('supports cascaded deletes', async()=>{
|
||||
const affectedRows = await DELETE.from (Cats) .where ({ID:[102,106]})
|
||||
expect (affectedRows) .to.be.greaterThan (0)
|
||||
const expected = [
|
||||
await expect (SELECT`ID,name`.from(Cats) ).to.eventually.eql ([
|
||||
{ ID:100, name:'Some Cats...' },
|
||||
{ ID:101, name:'Cat' },
|
||||
{ ID:108, name:'Catweazle' }
|
||||
]
|
||||
expect ( await SELECT`ID,name`.from(Cats) ).to.eql (expected)
|
||||
])
|
||||
})
|
||||
|
||||
})
|
||||
37
bookshop/test/hierarchical-data/requests.http
Normal file
37
bookshop/test/hierarchical-data/requests.http
Normal file
@@ -0,0 +1,37 @@
|
||||
#################################################
|
||||
#
|
||||
# Genres
|
||||
#
|
||||
|
||||
GET http://localhost:4004/odata/v4/test/Genres?
|
||||
###
|
||||
|
||||
GET http://localhost:4004/odata/v4/test/Genres?
|
||||
&$filter=parent_ID eq null&$select=name
|
||||
&$expand=children($select=name)
|
||||
###
|
||||
|
||||
POST http://localhost:4004/odata/v4/test/Genres?
|
||||
Content-Type: application/json
|
||||
|
||||
{ "ID":"100aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "name":"Some Sample Genres...", "children":[
|
||||
{ "ID":"101aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "name":"Cat", "children":[
|
||||
{ "ID":"102aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "name":"Kitty", "children":[
|
||||
{ "ID":"103aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "name":"Aristocat" },
|
||||
{ "ID":"104aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "name":"Kitty Bat" } ]},
|
||||
{ "ID":"105aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "name":"Catwoman", "children":[
|
||||
{ "ID":"106aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "name":"Catalina" } ]} ]},
|
||||
{ "ID":"107aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "name":"Catweazle" }
|
||||
]}
|
||||
###
|
||||
|
||||
GET http://localhost:4004/odata/v4/test/Genres(100aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)?
|
||||
&$expand=children
|
||||
&$expand=children($expand=children($expand=children($expand=children)))
|
||||
###
|
||||
|
||||
DELETE http://localhost:4004/odata/v4/test/Genres(103aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)
|
||||
###
|
||||
|
||||
DELETE http://localhost:4004/odata/v4/test/Genres(100aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)
|
||||
###
|
||||
@@ -1,4 +1,4 @@
|
||||
using { sap.capire.bookshop as my } from '../db/schema';
|
||||
using { sap.capire.bookshop as my } from '../../db/schema';
|
||||
service TestService {
|
||||
entity Genres as projection on my.Genres;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@cap-js/sqlite": "^1"
|
||||
"@cap-js/sqlite": "*"
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
const cds = require('@sap/cds/lib')
|
||||
const cds = require('@sap/cds')
|
||||
const { GET, expect } = cds.test (__dirname)
|
||||
cds.User.default = cds.User.Privileged // hard core monkey patch
|
||||
|
||||
describe('cap/samples - Localized Data', () => {
|
||||
|
||||
const { GET, expect } = cds.test (__dirname)
|
||||
beforeAll(()=>{
|
||||
cds.User.default = cds.User.Privileged // hard core monkey patch
|
||||
})
|
||||
|
||||
|
||||
it('serves localized $metadata documents', async () => {
|
||||
const { data } = await GET(`/browse/$metadata?sap-language=de`, { headers: { 'accept-language': 'de' }})
|
||||
expect(data).to.contain('<Annotation Term="Common.Label" String="Währung"/>')
|
||||
@@ -1,8 +1,8 @@
|
||||
const cds = require('@sap/cds/lib')
|
||||
const cds = require('@sap/cds')
|
||||
const { expect } = cds.test.in(__dirname,'..')
|
||||
|
||||
describe('cap/samples - Messaging', ()=>{
|
||||
|
||||
const { expect } = cds.test.in(__dirname,'..')
|
||||
const _model = '@capire/reviews'
|
||||
const Reviews = 'sap.capire.reviews.Reviews'
|
||||
beforeAll(()=>{
|
||||
101
bookshop/test/odata.test.js
Normal file
101
bookshop/test/odata.test.js
Normal file
@@ -0,0 +1,101 @@
|
||||
const cds = require('@sap/cds')
|
||||
const { GET, expect, axios } = cds.test ('@capire/bookshop')
|
||||
axios.defaults.auth = { username: 'alice', password: 'admin' }
|
||||
|
||||
describe('cap/samples - Bookshop APIs', () => {
|
||||
|
||||
it('serves $metadata documents in v4', async () => {
|
||||
const { headers, status, data } = await GET `/browse/$metadata`
|
||||
expect(status).to.equal(200)
|
||||
expect(headers).to.contain({
|
||||
// 'content-type': 'application/xml', //> fails with 'application/xml;charset=utf-8', which is set by express
|
||||
'odata-version': '4.0',
|
||||
})
|
||||
expect(headers['content-type']).to.match(/application\/xml/)
|
||||
expect(data).to.contain('<EntitySet Name="Books" EntityType="CatalogService.Books">')
|
||||
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>')
|
||||
})
|
||||
|
||||
it('serves ListOfBooks?$expand=genre,currency', async () => {
|
||||
const Mystery = { name: 'Mystery' }
|
||||
const Romance = { name: 'Romance' }
|
||||
const USD = { code: 'USD', name: 'US Dollar', descr: null, symbol: '$' }
|
||||
const { data } = await GET `/browse/ListOfBooks ${{
|
||||
params: { $search: 'Po', $select: `title,author`, $expand:`genre,currency` },
|
||||
}}`
|
||||
expect(data.value).to.containSubset([
|
||||
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe', genre:Mystery, currency:USD },
|
||||
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe', genre:Romance, currency:USD },
|
||||
])
|
||||
})
|
||||
|
||||
describe('query options...', () => {
|
||||
|
||||
it('supports $search in multiple fields', async () => {
|
||||
const { data } = await GET `/browse/Books ${{
|
||||
params: { $search: 'Po', $select: `title,author` },
|
||||
}}`
|
||||
expect(data.value).to.containSubset([
|
||||
{ ID: 201, title: 'Wuthering Heights', author: 'Emily Brontë' },
|
||||
{ ID: 207, title: 'Jane Eyre', author: 'Charlotte Brontë' },
|
||||
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe' },
|
||||
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe' },
|
||||
])
|
||||
})
|
||||
|
||||
it('supports $select', async () => {
|
||||
const { data } = await GET(`/browse/Books`, {
|
||||
params: { $select: `ID,title` },
|
||||
})
|
||||
expect(data.value).to.containSubset([
|
||||
{ ID: 201, title: 'Wuthering Heights' },
|
||||
{ ID: 207, title: 'Jane Eyre' },
|
||||
{ ID: 251, title: 'The Raven' },
|
||||
{ ID: 252, title: 'Eleonora' },
|
||||
{ ID: 271, title: 'Catweazle' },
|
||||
])
|
||||
})
|
||||
|
||||
it('supports $expand', async () => {
|
||||
const { data } = await GET(`/admin/Authors`, {
|
||||
params: {
|
||||
$select: `name`,
|
||||
$expand: `books($select=title)`,
|
||||
},
|
||||
})
|
||||
expect(data.value).to.containSubset([
|
||||
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
|
||||
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
|
||||
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
|
||||
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('supports $value requests', async () => {
|
||||
const { data } = await GET`/admin/Books/201/stock/$value`
|
||||
expect(data).to.equal(12)
|
||||
})
|
||||
|
||||
it('supports $top/$skip paging', async () => {
|
||||
const { data: p1 } = await GET`/browse/Books?$select=title&$top=3`
|
||||
expect(p1.value).to.containSubset([
|
||||
{ ID: 201, title: 'Wuthering Heights' },
|
||||
{ ID: 207, title: 'Jane Eyre' },
|
||||
{ ID: 251, title: 'The Raven' },
|
||||
])
|
||||
const { data: p2 } = await GET`/browse/Books?$select=title&$skip=3`
|
||||
expect(p2.value).to.containSubset([
|
||||
{ ID: 252, title: 'Eleonora' },
|
||||
{ ID: 271, title: 'Catweazle' },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('serves user info', async () => {
|
||||
const { data: alice } = await GET `/user/me`
|
||||
expect(alice).to.containSubset({ id: 'alice' })
|
||||
const { data: joe } = await GET (`/user/me`, {auth: { username: 'joe' }})
|
||||
expect(joe).to.containSubset({ id: 'joe' })
|
||||
})
|
||||
|
||||
})
|
||||
@@ -40,8 +40,7 @@ Authorization: Basic alice:
|
||||
|
||||
{
|
||||
"ID": 112,
|
||||
"name": "Shakespeeeeere",
|
||||
"age": 22
|
||||
"name": "Shakespeeeeere"
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +55,7 @@ Authorization: Basic alice:
|
||||
"title": "Poems : Pocket Poets",
|
||||
"descr": "The Everyman's Library Pocket Poets hardcover series is popular for its compact size and reasonable price which does not compromise content. Poems: Bronte contains poems that demonstrate a sensibility elemental in its force with an imaginative discipline and flexibility of the highest order. Also included are an Editor's Note and an index of first lines.",
|
||||
"author": { "ID": 101 },
|
||||
"genre": { "ID": 12 },
|
||||
"genre": { "ID": "12aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" },
|
||||
"stock": 5,
|
||||
"price": "12.05",
|
||||
"currency": { "code": "USD" }
|
||||
126
bookshop/test/protocols/hcql-adapter.test.js
Normal file
126
bookshop/test/protocols/hcql-adapter.test.js
Normal file
@@ -0,0 +1,126 @@
|
||||
const cds = require('@sap/cds/lib')
|
||||
const { GET, expect, axios } = cds.test(__dirname)
|
||||
|
||||
// Fetch API disallows GET|HEAD requests with body
|
||||
if (axios.constructor.name === 'Naxios') it = it.skip
|
||||
|
||||
describe ('GET w/ query in body', () => {
|
||||
|
||||
it ('serves CQN query objects in body', async () => {
|
||||
const {data:books} = await GET ('/hcql/admin', {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: cds.ql `SELECT from Books`
|
||||
})
|
||||
expect(books).to.be.an('array').of.length(5)
|
||||
})
|
||||
|
||||
it ('serves plain CQL strings in body', async () => {
|
||||
const {data:books} = await GET ('/hcql/admin', {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
data: `SELECT from Books`
|
||||
})
|
||||
expect(books).to.be.an('array').of.length(5)
|
||||
})
|
||||
|
||||
it ('serves complex and deep queries', async () => {
|
||||
const {data:books} = await GET ('/hcql/admin', {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
data: `SELECT from Authors {
|
||||
name,
|
||||
books [order by title] {
|
||||
title,
|
||||
genre.name as genre
|
||||
}
|
||||
}`
|
||||
})
|
||||
expect(books).to.deep.equal([
|
||||
{
|
||||
name: "Emily Brontë",
|
||||
books: [
|
||||
{ title: "Wuthering Heights", genre: 'Drama' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Charlotte Brontë",
|
||||
books: [
|
||||
{ title: "Jane Eyre", genre: 'Drama' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Edgar Allen Poe",
|
||||
books: [
|
||||
{ title: "Eleonora", genre: 'Romance' },
|
||||
{ title: "The Raven", genre: 'Mystery' },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Richard Carpenter",
|
||||
books: [
|
||||
{ title: "Catweazle", genre: 'Fantasy' }
|
||||
]
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
describe ('Sluggified variants', () => {
|
||||
|
||||
test ('GET /Books', async () => {
|
||||
const {data:books} = await GET ('/hcql/admin/Books')
|
||||
expect(books).to.be.an('array').of.length(5)
|
||||
expect(books.length).to.eql(5) //.of.length(5)
|
||||
})
|
||||
|
||||
|
||||
test ('GET /Books/201', async () => {
|
||||
const {data:book} = await GET ('/hcql/admin/Books/201')
|
||||
expect(book).to.be.an('object')
|
||||
expect(book).to.have.property ('title', "Wuthering Heights")
|
||||
})
|
||||
|
||||
test ('GET /Books { title, author.name as author }' , async () => {
|
||||
const {data:books} = await GET ('/hcql/admin/Books { title, author.name as author } order by ID')
|
||||
expect(books).to.deep.equal ([
|
||||
{ title: "Wuthering Heights", author: "Emily Brontë" },
|
||||
{ title: "Jane Eyre", author: "Charlotte Brontë" },
|
||||
{ title: "The Raven", author: "Edgar Allen Poe" },
|
||||
{ title: "Eleonora", author: "Edgar Allen Poe" },
|
||||
{ title: "Catweazle", author: "Richard Carpenter" }
|
||||
])
|
||||
})
|
||||
|
||||
test ('GET /Books/201 w/ CQL tail in URL' , async () => {
|
||||
const {data:book} = await GET ('/hcql/admin/Books/201 { title, author.name as author } order by ID')
|
||||
expect(book).to.deep.equal ({ title: "Wuthering Heights", author: "Emily Brontë" })
|
||||
})
|
||||
|
||||
it ('GET /Books/201 w/ CQL fragment in body' , async () => {
|
||||
const {data:book} = await GET ('/hcql/admin/Books/201', {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
data: `{ title, author.name as author }`
|
||||
})
|
||||
expect(book).to.deep.equal ({ title: "Wuthering Heights", author: "Emily Brontë" })
|
||||
})
|
||||
|
||||
it ('GET /Books/201 w/ CQN fragment in body' , async () => {
|
||||
const {data:book} = await GET ('/hcql/admin/Books/201', {
|
||||
data: cds.ql `SELECT title, author.name as author` .SELECT
|
||||
})
|
||||
expect(book).to.deep.equal ({ title: "Wuthering Heights", author: "Emily Brontë" })
|
||||
})
|
||||
|
||||
it ('GET /Books/201 w/ tail in URL plus CQL/CQN fragments in body' , async () => {
|
||||
const {data:[b1]} = await GET ('/hcql/admin/Books where ID=201', {
|
||||
data: cds.ql `SELECT title, author.name as author` .SELECT
|
||||
})
|
||||
expect(b1).to.deep.equal ({ title: "Wuthering Heights", author: "Emily Brontë" })
|
||||
const {data:[b2]} = await GET ('/hcql/admin/Books where ID=201', {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
data: `{ title, author.name as author }`
|
||||
})
|
||||
expect(b2).to.deep.equal ({ title: "Wuthering Heights", author: "Emily Brontë" })
|
||||
})
|
||||
|
||||
})
|
||||
225
bookshop/test/protocols/hcql.http
Normal file
225
bookshop/test/protocols/hcql.http
Normal file
@@ -0,0 +1,225 @@
|
||||
@server = http://localhost:4004
|
||||
|
||||
GET {{server}}/odata/v4/admin/Authors?
|
||||
&$select=ID,name
|
||||
&$expand=books($select=ID,title)
|
||||
&$count=true
|
||||
###
|
||||
|
||||
#
|
||||
# The basic variant expects a CQN object passed as an application/json body
|
||||
# to a POST request. This is also the fastest one, as it doesn't need CQL parsing.
|
||||
# Note: $count is returned in X-Total-Count response header
|
||||
#
|
||||
GET {{server}}/hcql/admin
|
||||
Content-Type: application/json
|
||||
# Accept-Language: de
|
||||
|
||||
{ "SELECT": {
|
||||
"from": { "ref": [ "Authors" ] },
|
||||
"columns": [
|
||||
{ "ref": [ "name" ] },
|
||||
{ "ref": [ "books" ], "expand": [
|
||||
{ "ref": [ "ID" ] },
|
||||
{ "ref": [ "title" ] }
|
||||
]}
|
||||
],
|
||||
"count": true
|
||||
}}
|
||||
###
|
||||
|
||||
POST {{server}}/hcql/browse/submitOrder?book=201&quantity=2
|
||||
Authorization: Basic alice:
|
||||
###
|
||||
|
||||
POST {{server}}/hcql/browse/submitOrder
|
||||
Authorization: Basic alice:
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"book": 201,
|
||||
"quantity": 2
|
||||
}
|
||||
###
|
||||
|
||||
GET {{server}}/hcql/browse/submitOrder?book=201&quantity=2
|
||||
Authorization: Basic alice:
|
||||
###
|
||||
|
||||
#
|
||||
# Alternatively you can pass a CQL string as plain/text body
|
||||
#
|
||||
GET {{server}}/hcql/admin
|
||||
Content-Type: text/plain
|
||||
# X-Total-Count: true
|
||||
|
||||
SELECT from Authors { name, books { title }}
|
||||
# SELECT from Books { title, currency }
|
||||
###
|
||||
|
||||
#
|
||||
# In addition we offer convenience slug routes...
|
||||
# .e.g. /srv/entity routes
|
||||
#
|
||||
|
||||
|
||||
GET {{server}}/hcql/admin/Books
|
||||
###
|
||||
|
||||
GET {{server}}/hcql/admin/Books/201
|
||||
###
|
||||
|
||||
GET {{server}}/hcql/admin/Books { ID, title, author.name as author }
|
||||
###
|
||||
|
||||
GET {{server}}/hcql/admin/Books order by stock desc
|
||||
Content-Type: text/plain
|
||||
|
||||
{ title, stock }
|
||||
###
|
||||
|
||||
GET {{server}}/hcql/admin/Books/201 { ID, title, author.name }
|
||||
###
|
||||
|
||||
GET {{server}}/hcql/admin/Books/201 { ID, title, author{name} }
|
||||
###
|
||||
|
||||
|
||||
POST {{server}}/hcql/admin/Books?title=The Black Cat&author_ID=101
|
||||
###
|
||||
|
||||
|
||||
POST {{server}}/hcql/admin/Books?title=The Black Cat
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"author_ID": 101
|
||||
}
|
||||
###
|
||||
|
||||
POST {{server}}/hcql/admin/Books
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "The Black Cat",
|
||||
"author": { "ID": 101 }
|
||||
}
|
||||
###
|
||||
|
||||
PUT {{server}}/hcql/admin/Books/275?title=Catastrophe
|
||||
###
|
||||
|
||||
PATCH {{server}}/hcql/admin/Books/275
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "Catastrophe"
|
||||
}
|
||||
###
|
||||
|
||||
GET {{server}}/hcql/admin/Authors { name, books { ID, title }}
|
||||
###
|
||||
|
||||
GET {{server}}/hcql/admin/Books { ID, title, author.name as author } order by ID desc
|
||||
###
|
||||
|
||||
|
||||
|
||||
// ------------------------------------
|
||||
|
||||
POST {{server}}/hcql/admin
|
||||
Content-Type: application/json
|
||||
|
||||
{"SELECT": { "from": { "ref": ["Books"] }}}
|
||||
###
|
||||
|
||||
POST {{server}}/hcql/admin
|
||||
Content-Type: text/plain
|
||||
|
||||
SELECT from Authors {
|
||||
name as author,
|
||||
books {
|
||||
title,
|
||||
stock,
|
||||
price,
|
||||
currency { * }
|
||||
}
|
||||
}
|
||||
where name like '%Bro%'
|
||||
order by name asc
|
||||
###
|
||||
|
||||
|
||||
#
|
||||
# Simple REST-style URLs as supported as well
|
||||
#
|
||||
|
||||
GET {{server}}/hcql/admin/Books
|
||||
###
|
||||
|
||||
GET {{server}}/hcql/admin/Books/201
|
||||
###
|
||||
|
||||
|
||||
#
|
||||
# REST-style URLs can be combined with trailing CQL in the path, in plain
|
||||
# text body, or with projections sent as application/json array
|
||||
#
|
||||
|
||||
GET {{server}}/hcql/admin/Books order by stock desc
|
||||
###
|
||||
|
||||
GET {{server}}/hcql/admin/Books { title as book, stock } order by stock desc
|
||||
###
|
||||
|
||||
GET {{server}}/hcql/admin/Authors
|
||||
Content-Type: text/plain
|
||||
Accept-Language: fr
|
||||
|
||||
{
|
||||
ID, name as author,
|
||||
books {
|
||||
title,
|
||||
stock,
|
||||
currency { * }
|
||||
}
|
||||
}
|
||||
where name like '%Bro%'
|
||||
order by name asc
|
||||
###
|
||||
|
||||
|
||||
GET {{server}}/hcql/admin/Books/201 { title, stock }
|
||||
###
|
||||
|
||||
GET {{server}}/hcql/admin/Books order by stock desc
|
||||
Content-Type: text/plain
|
||||
|
||||
{ title, stock }
|
||||
###
|
||||
|
||||
|
||||
#
|
||||
# CQL adaptor also provides access to the underlying CSN schema
|
||||
#
|
||||
|
||||
GET {{server}}/hcql/admin/$csn
|
||||
###
|
||||
|
||||
|
||||
|
||||
#
|
||||
# CQL adaptor also supports INSERTs, UPDATEs, DELETEs ...
|
||||
#
|
||||
|
||||
POST {{server}}/hcql/admin
|
||||
Content-Type: application/jsonin wonderland
|
||||
|
||||
{ "INSERT": {
|
||||
"into": "Books",
|
||||
"entries": [{
|
||||
"title": "The Black Cat",
|
||||
"author": { "ID": 150 }
|
||||
}]
|
||||
}}
|
||||
###
|
||||
26
bookshop/test/protocols/odata.http
Normal file
26
bookshop/test/protocols/odata.http
Normal file
@@ -0,0 +1,26 @@
|
||||
@server = http://localhost:4004
|
||||
|
||||
GET {{server}}/odata/v2/admin/Authors
|
||||
Authorization: Basic alice:
|
||||
###
|
||||
|
||||
GET {{server}}/odata/v2/admin/Authors?$select=ID,name&$expand=books($select=ID,title)
|
||||
Authorization: Basic alice:
|
||||
###
|
||||
|
||||
GET {{server}}/odata/v4/admin/Authors
|
||||
Authorization: Basic alice:
|
||||
###
|
||||
|
||||
GET {{server}}/odata/v4/admin/Authors?$select=ID,name&$expand=books($select=ID,title)
|
||||
Authorization: Basic alice:
|
||||
###
|
||||
|
||||
|
||||
GET {{server}}/rest/admin/Authors
|
||||
Authorization: Basic alice:
|
||||
###
|
||||
|
||||
GET {{server}}/rest/admin/Authors?$select=ID,name&$expand=books($select=ID,title)
|
||||
Authorization: Basic alice:
|
||||
###
|
||||
9
bookshop/test/protocols/rest.http
Normal file
9
bookshop/test/protocols/rest.http
Normal file
@@ -0,0 +1,9 @@
|
||||
@server = http://localhost:4004
|
||||
|
||||
GET {{server}}/rest/admin/Authors
|
||||
Authorization: Basic alice:
|
||||
###
|
||||
|
||||
GET {{server}}/rest/admin/Authors?$select=ID,name&$expand=books($select=ID,title)
|
||||
Authorization: Basic alice:
|
||||
###
|
||||
4
bookshop/test/protocols/services.cds
Normal file
4
bookshop/test/protocols/services.cds
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
using { CatalogService, AdminService } from '@capire/bookstore';
|
||||
annotate CatalogService with @hcql @odata @path:'browse' @requires:[];
|
||||
annotate AdminService with @hcql @odata @path:'admin';
|
||||
@@ -1,14 +1,12 @@
|
||||
Books = Bücher
|
||||
Book = Buch
|
||||
ID = ID
|
||||
Title = Titel
|
||||
Description = Beschreibung
|
||||
Stock = Bestand
|
||||
Image = Bild
|
||||
Price = Preis
|
||||
Currency = Währung
|
||||
|
||||
Authors = Autoren
|
||||
Author = Autor
|
||||
AuthorID = ID des Autors
|
||||
AuthorName = Name des Autors
|
||||
Age = Alter
|
||||
Name = Name
|
||||
Stock = Bestand
|
||||
Order = Bestellung
|
||||
Orders = Bestellungen
|
||||
Price = Preis
|
||||
21
bookstore/_i18n/i18n_en.properties
Normal file
21
bookstore/_i18n/i18n_en.properties
Normal file
@@ -0,0 +1,21 @@
|
||||
Books = Books
|
||||
Book = Book
|
||||
ID = ID
|
||||
Title = Title
|
||||
Description = Description
|
||||
Stock = Stock
|
||||
Image = Image
|
||||
Price = Price
|
||||
Currency = Currency
|
||||
|
||||
Authors = Authors
|
||||
Author = Author
|
||||
AuthorID = Author''s ID
|
||||
Name = Name
|
||||
DateOfBirth = Date of Birth
|
||||
DateOfDeath = Date of Death
|
||||
PlaceOfBirth = Place of Birth
|
||||
PlaceOfDeath = Place of Death
|
||||
|
||||
Genres = Genres
|
||||
Genre = Genre
|
||||
20
bookstore/_i18n/i18n_fr.properties
Normal file
20
bookstore/_i18n/i18n_fr.properties
Normal file
@@ -0,0 +1,20 @@
|
||||
Books = Livres
|
||||
Book = Livre
|
||||
Title = Titre
|
||||
Description = Description
|
||||
Stock = Action
|
||||
Image = Image
|
||||
Price = Prix
|
||||
Currency = Devise
|
||||
|
||||
Authors = Auteurs
|
||||
Author = Auteur
|
||||
AuthorID = ID de l''auteur
|
||||
Name = Nom
|
||||
DateOfBirth = Date de naissance
|
||||
DateOfDeath = Date de décès
|
||||
PlaceOfBirth = Lieu de naissance
|
||||
PlaceOfDeath = Lieu de décès
|
||||
|
||||
Genres = Genre
|
||||
Genre = Genre
|
||||
34
bookstore/_i18n/messages_de.properties
Normal file
34
bookstore/_i18n/messages_de.properties
Normal file
@@ -0,0 +1,34 @@
|
||||
400 = Ungültige Anfrage
|
||||
401 = Nicht autorisiert
|
||||
403 = Verboten
|
||||
404 = Nicht gefunden
|
||||
405 = Methode nicht zulässig
|
||||
406 = Nicht akzeptabel
|
||||
407 = Proxy-Authentifizierung erforderlich
|
||||
408 = Anfrage-Timeout
|
||||
409 = Konflikt
|
||||
410 = Weg
|
||||
411 = Erforderliche Länge
|
||||
412 = Vorbedingung fehlgeschlagen
|
||||
413 = Nutzlast zu groß
|
||||
414 = URI zu lang
|
||||
415 = Nicht unterstützter Medientyp
|
||||
416 = Bereich nicht erfüllbar
|
||||
417 = Erwartung fehlgeschlagen
|
||||
422 = Nicht verarbeitbarer Inhalt
|
||||
424 = Fehlgeschlagene Abhängigkeit
|
||||
428 = Vorbedingung erforderlich
|
||||
429 = Zu viele Anfragen
|
||||
431 = Anfrage-Headerfelder zu groß
|
||||
451 = Aus rechtlichen Gründen nicht verfügbar
|
||||
500 = Interner Serverfehler
|
||||
501 = Der Server unterstützt die zur Erfüllung der Anfrage erforderliche Funktionalität nicht
|
||||
502 = Ungültiges Gateway
|
||||
503 = Dienst nicht verfügbar
|
||||
504 = Gateway-Timeout
|
||||
|
||||
ASSERT_RANGE = Wert {0} liegt nicht im angegebenen Bereich [{1}, {2}]
|
||||
ASSERT_FORMAT = Wert "{0}" liegt nicht im angegebenen Format "{1}"
|
||||
ASSERT_ARRAY = Wert muss ein Array sein
|
||||
ASSERT_ENUM = Wert {0} ist gemäß Enumerationsdeklaration {{1}} ungültig
|
||||
ASSERT_NOT_NULL = Wert ist erforderlich
|
||||
34
bookstore/_i18n/messages_en.properties
Normal file
34
bookstore/_i18n/messages_en.properties
Normal file
@@ -0,0 +1,34 @@
|
||||
400 = Bad Request
|
||||
401 = Unauthorized
|
||||
403 = Forbidden
|
||||
404 = Not Found
|
||||
405 = Method Not Allowed
|
||||
406 = Not Acceptable
|
||||
407 = Proxy Authentication Required
|
||||
408 = Request Timeout
|
||||
409 = Conflict
|
||||
410 = Gone
|
||||
411 = Length Required
|
||||
412 = Precondition Failed
|
||||
413 = Payload Too Large
|
||||
414 = URI Too Long
|
||||
415 = Unsupported Media Type
|
||||
416 = Range Not Satisfiable
|
||||
417 = Expectation Failed
|
||||
422 = Unprocessable Content
|
||||
424 = Failed Dependency
|
||||
428 = Precondition Required
|
||||
429 = Too Many Requests
|
||||
431 = Request Header Fields Too Large
|
||||
451 = Unavailable For Legal Reasons
|
||||
500 = Internal Server Error
|
||||
501 = The server does not support the functionality required to fulfill the request
|
||||
502 = Bad Gateway
|
||||
503 = Service Unavailable
|
||||
504 = Gateway Timeout
|
||||
|
||||
ASSERT_RANGE = Value {0} is not in specified range [{1}, {2}]
|
||||
ASSERT_FORMAT = Value "{0}" is not in specified format "{1}"
|
||||
ASSERT_ARRAY = Value must be an array
|
||||
ASSERT_ENUM = Value {0} is invalid according to enum declaration {{1}}
|
||||
ASSERT_NOT_NULL = Value is required
|
||||
34
bookstore/_i18n/messages_fr.properties
Normal file
34
bookstore/_i18n/messages_fr.properties
Normal file
@@ -0,0 +1,34 @@
|
||||
400 = Requête incorrecte
|
||||
401 = Non autorisée
|
||||
403 = Interdite
|
||||
404 = Introuvable
|
||||
405 = Méthode non autorisée
|
||||
406 = Non acceptable
|
||||
407 = Authentification proxy requise
|
||||
408 = Délai d''expiration de la requête
|
||||
409 = Conflit
|
||||
410 = Disparu
|
||||
411 = Longueur requise
|
||||
412 = Échec de la condition préalable
|
||||
413 = Charge utile trop importante
|
||||
414 = URI trop longue
|
||||
415 = Type de média non pris en charge
|
||||
416 = Plage non satisfaisante
|
||||
417 = Échec de l''attente
|
||||
422 = Contenu non traitable
|
||||
424 = Dépendance échouée
|
||||
428 = Condition préalable requise
|
||||
429 = Trop de requêtes
|
||||
431 = Champs d''en-tête de requête trop importants
|
||||
451 = Indisponible pour des raisons juridiques
|
||||
500 = Erreur interne du serveur
|
||||
501 = Le serveur ne prend pas en charge la fonctionnalité requise pour répondre à la requête
|
||||
502 = Passerelle incorrecte
|
||||
503 = Service indisponible
|
||||
504 = Délai d''attente de la passerelle
|
||||
|
||||
ASSERT_RANGE = La valeur {0} n''est pas dans la plage spécifiée [{1}, {2}]
|
||||
ASSERT_FORMAT = La valeur "{0}" n''est pas au format spécifié "{1}"
|
||||
ASSERT_ARRAY = La valeur doit être un tableau
|
||||
ASSERT_ENUM = La valeur {0} n''est pas valide selon la déclaration d''énumération {{1}}
|
||||
ASSERT_NOT_NULL = La valeur est obligatoire
|
||||
5
bookstore/app/_i18n/i18n_de.properties
Normal file
5
bookstore/app/_i18n/i18n_de.properties
Normal file
@@ -0,0 +1,5 @@
|
||||
AuthorName = Name des Autors
|
||||
Age = Alter
|
||||
rder = Bestellung
|
||||
Orders = Bestellungen
|
||||
Price = Preis
|
||||
8
bookstore/app/_i18n/i18n_en.properties
Normal file
8
bookstore/app/_i18n/i18n_en.properties
Normal file
@@ -0,0 +1,8 @@
|
||||
Age = Age
|
||||
Lifetime = Lifetime
|
||||
|
||||
SubGenres = Subgenre
|
||||
|
||||
NumCode = Numeric Code
|
||||
MinorUnit = Minor Unit
|
||||
Exponent = Exponent
|
||||
@@ -1,6 +1,8 @@
|
||||
using { AdminService } from '@capire/bookstore';
|
||||
using from '../common'; // to help UI linter get the complete annotations
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Books Object Page
|
||||
@@ -40,7 +42,34 @@ annotate AdminService.Books with @(
|
||||
}
|
||||
);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Value Help for Tree Table
|
||||
//
|
||||
annotate AdminService.Books with {
|
||||
genre @(Common: {
|
||||
Label : 'Genre',
|
||||
ValueList: {
|
||||
CollectionPath : 'Genres',
|
||||
Parameters : [
|
||||
{
|
||||
$Type : 'Common.ValueListParameterDisplayOnly',
|
||||
ValueListProperty: 'name',
|
||||
},
|
||||
{
|
||||
$Type : 'Common.ValueListParameterInOut',
|
||||
LocalDataProperty: genre_ID,
|
||||
ValueListProperty: 'ID',
|
||||
}
|
||||
],
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Hide ID because of the ValueHelp
|
||||
annotate AdminService.Genres with {
|
||||
ID @UI.Hidden;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
@@ -81,6 +110,3 @@ extend service AdminService {
|
||||
|
||||
// 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; }
|
||||
@@ -3,7 +3,6 @@
|
||||
"LaunchPage": {
|
||||
"adapter": {
|
||||
"config": {
|
||||
"catalogs": [],
|
||||
"groups": [
|
||||
{
|
||||
"id": "Bookshop",
|
||||
@@ -24,7 +23,7 @@
|
||||
"id": "BrowseGenres",
|
||||
"tileType": "sap.ushell.ui.tile.StaticTile",
|
||||
"properties": {
|
||||
"title": "Browse Genres (OData v2)",
|
||||
"title": "Browse Genres",
|
||||
"targetURL": "#Genres-display"
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ using CatalogService from '@capire/bookstore';
|
||||
//
|
||||
annotate CatalogService.Books with @(UI : {
|
||||
HeaderInfo : {
|
||||
TypeName : 'Book',
|
||||
TypeNamePlural : 'Books',
|
||||
TypeName : '{i18n>Book}',
|
||||
TypeNamePlural : '{i18n>Books}',
|
||||
Description : {Value : author}
|
||||
},
|
||||
HeaderFacets : [{
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using { sap.capire.bookshop as my } from '@capire/bookstore';
|
||||
using { sap.common } from '@capire/common';
|
||||
using { sap.common.Currencies } from '@sap/cds/common';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
@@ -38,7 +37,7 @@ annotate my.Books with @(
|
||||
author @ValueList.entity : 'Authors';
|
||||
};
|
||||
|
||||
annotate Currencies with {
|
||||
annotate common.Currencies with {
|
||||
symbol @Common.Label : '{i18n>Currency}';
|
||||
}
|
||||
|
||||
@@ -69,56 +68,6 @@ annotate my.Books with {
|
||||
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
|
||||
31
bookstore/app/fiori-apps.html
Normal file
31
bookstore/app/fiori-apps.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Bookshop</title>
|
||||
|
||||
<script>
|
||||
window["sap-ushell-config"] = {
|
||||
defaultRenderer: "fiori2",
|
||||
applications: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<script id="sap-ushell-bootstrap" src="https://ui5.sap.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
|
||||
<script id="sap-ui-bootstrap" src="https://ui5.sap.com/resources/sap-ui-core.js"
|
||||
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout" data-sap-ui-compatVersion="edge"
|
||||
data-sap-ui-theme="sap_horizon"></script>
|
||||
<script>
|
||||
sap.ui.getCore().attachInit(() =>
|
||||
sap.ushell.Container.createRenderer().placeAt("content")
|
||||
);
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="sapUiBody" id="content">
|
||||
</body>
|
||||
|
||||
</html>
|
||||
33
bookstore/app/genres/fiori-service.cds
Normal file
33
bookstore/app/genres/fiori-service.cds
Normal file
@@ -0,0 +1,33 @@
|
||||
using { sap.capire.bookshop.Genres } from '@capire/bookstore';
|
||||
|
||||
annotate Genres with @cds.search: {name};
|
||||
annotate Genres with @readonly;
|
||||
annotate Genres with {
|
||||
name @title: '{i18n>Genre}';
|
||||
}
|
||||
|
||||
// Lists
|
||||
annotate Genres with @(
|
||||
Common.SemanticKey : [name],
|
||||
UI.SelectionFields : [name],
|
||||
UI.LineItem : [
|
||||
{ Value: name, Label: '{i18n>Name}' },
|
||||
],
|
||||
);
|
||||
|
||||
// Details
|
||||
annotate Genres with @(UI : {
|
||||
Identification : [{ Value: name }],
|
||||
HeaderInfo : {
|
||||
TypeName : '{i18n>Genre}',
|
||||
TypeNamePlural : '{i18n>Genres}',
|
||||
Title : { Value: name },
|
||||
Description : { Value: ID }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Tree Views
|
||||
// annotate AdminService.Genres with @hierarchy; // upcomming simplification
|
||||
using from './tree-view';
|
||||
using from './value-help';
|
||||
42
bookstore/app/genres/tree-view.cds
Normal file
42
bookstore/app/genres/tree-view.cds
Normal file
@@ -0,0 +1,42 @@
|
||||
using { AdminService } from '@capire/bookstore';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Genres Tree View
|
||||
//
|
||||
|
||||
// Tell Fiori about the structure of the hierarchy
|
||||
annotate AdminService.Genres with @Aggregation.RecursiveHierarchy #GenresHierarchy : {
|
||||
ParentNavigationProperty : parent, // navigates to a node's parent
|
||||
NodeProperty : ID, // identifies a node, usually the key
|
||||
};
|
||||
|
||||
// Fiori expects the following to be defined explicitly, even though they're always the same
|
||||
extend AdminService.Genres with @(
|
||||
// The columns expected by Fiori to be present in hierarchy entities
|
||||
Hierarchy.RecursiveHierarchy #GenresHierarchy : {
|
||||
LimitedDescendantCount : LimitedDescendantCount,
|
||||
DistanceFromRoot : DistanceFromRoot,
|
||||
DrillState : DrillState,
|
||||
LimitedRank : LimitedRank
|
||||
},
|
||||
// Disallow filtering on these properties from Fiori UIs
|
||||
Capabilities.FilterRestrictions.NonFilterableProperties: [
|
||||
'LimitedDescendantCount',
|
||||
'DistanceFromRoot',
|
||||
'DrillState',
|
||||
'LimitedRank'
|
||||
],
|
||||
// Disallow sorting on these properties from Fiori UIs
|
||||
Capabilities.SortRestrictions.NonSortableProperties : [
|
||||
'LimitedDescendantCount',
|
||||
'DistanceFromRoot',
|
||||
'DrillState',
|
||||
'LimitedRank'
|
||||
],
|
||||
) columns { // Ensure we can query these fields from database
|
||||
null as LimitedDescendantCount : Int16,
|
||||
null as DistanceFromRoot : Int16,
|
||||
null as DrillState : String,
|
||||
null as LimitedRank : Int16,
|
||||
};
|
||||
6
bookstore/app/genres/value-help.cds
Normal file
6
bookstore/app/genres/value-help.cds
Normal file
@@ -0,0 +1,6 @@
|
||||
// Value help with Tree View
|
||||
using from '../admin-books/fiori-service';
|
||||
annotate AdminService.Books:genre with @Common.ValueList.PresentationVariantQualifier: 'VH';
|
||||
annotate AdminService.Genres with @UI.PresentationVariant #VH: {
|
||||
RecursiveHierarchyQualifier : 'GenresHierarchy',
|
||||
};
|
||||
3
bookstore/app/genres/webapp/Component.js
Normal file
3
bookstore/app/genres/webapp/Component.js
Normal file
@@ -0,0 +1,3 @@
|
||||
sap.ui.define(["sap/fe/core/AppComponent"], ac => ac.extend("genres.Component", {
|
||||
metadata:{ manifest:'json' }
|
||||
}))
|
||||
4
bookstore/app/genres/webapp/i18n/i18n.properties
Normal file
4
bookstore/app/genres/webapp/i18n/i18n.properties
Normal file
@@ -0,0 +1,4 @@
|
||||
#XTIT
|
||||
appTitle=Browse Genres
|
||||
#XTXT
|
||||
appDescription=Genres as Tree View
|
||||
2
bookstore/app/genres/webapp/i18n/i18n_de.properties
Normal file
2
bookstore/app/genres/webapp/i18n/i18n_de.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
appTitle=Zeige Genres
|
||||
appDescription=Genres als Baumansicht
|
||||
124
bookstore/app/genres/webapp/manifest.json
Normal file
124
bookstore/app/genres/webapp/manifest.json
Normal file
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"_version": "1.8.0",
|
||||
"sap.app": {
|
||||
"id": "genres",
|
||||
"type": "application",
|
||||
"title": "{{appTitle}}",
|
||||
"description": "{{appDescription}}",
|
||||
"applicationVersion": {
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"dataSources": {
|
||||
"AdminService": {
|
||||
"uri": "admin/",
|
||||
"type": "OData",
|
||||
"settings": {
|
||||
"odataVersion": "4.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"crossNavigation": {
|
||||
"inbounds": {
|
||||
"Genres-display": {
|
||||
"signature": {
|
||||
"parameters": {},
|
||||
"additionalParameters": "allowed"
|
||||
},
|
||||
"semanticObject": "Genres",
|
||||
"action": "display"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sap.ui5": {
|
||||
"dependencies": {
|
||||
"minUI5Version": "1.122.0",
|
||||
"libs": {
|
||||
"sap.fe.templates": {}
|
||||
}
|
||||
},
|
||||
"models": {
|
||||
"i18n": {
|
||||
"type": "sap.ui.model.resource.ResourceModel",
|
||||
"uri": "i18n/i18n.properties"
|
||||
},
|
||||
"": {
|
||||
"dataSource": "AdminService",
|
||||
"settings": {
|
||||
"synchronizationMode": "None",
|
||||
"operationMode": "Server",
|
||||
"autoExpandSelect": true,
|
||||
"earlyRequests": true,
|
||||
"groupProperties": {
|
||||
"default": {
|
||||
"submit": "Auto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"routing": {
|
||||
"routes": [
|
||||
{
|
||||
"pattern": ":?query:",
|
||||
"name": "GenresList",
|
||||
"target": "GenresList"
|
||||
},
|
||||
{
|
||||
"pattern": "Genres({key}):?query:",
|
||||
"name": "GenresDetails",
|
||||
"target": "GenresDetails"
|
||||
}
|
||||
],
|
||||
"targets": {
|
||||
"GenresList": {
|
||||
"type": "Component",
|
||||
"id": "GenresList",
|
||||
"name": "sap.fe.templates.ListReport",
|
||||
"options": {
|
||||
"settings": {
|
||||
"contextPath": "/Genres",
|
||||
"navigation": {
|
||||
"Genres": {
|
||||
"detail": {
|
||||
"route": "GenresDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"controlConfiguration": {
|
||||
"@com.sap.vocabularies.UI.v1.LineItem": {
|
||||
"tableSettings": {
|
||||
"hierarchyQualifier": "GenresHierarchy",
|
||||
"type": "TreeTable"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"GenresDetails": {
|
||||
"type": "Component",
|
||||
"id": "GenresDetails",
|
||||
"name": "sap.fe.templates.ObjectPage",
|
||||
"options": {
|
||||
"settings": {
|
||||
"contextPath": "/Genres"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"contentDensities": {
|
||||
"compact": true,
|
||||
"cozy": true
|
||||
}
|
||||
},
|
||||
"sap.ui": {
|
||||
"technology": "UI5",
|
||||
"fullWidth": false
|
||||
},
|
||||
"sap.fiori": {
|
||||
"registrationIds": [],
|
||||
"archeType": "transactional"
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,6 @@ using from './admin-authors/fiori-service';
|
||||
using from './admin-books/fiori-service';
|
||||
using from './browse/fiori-service';
|
||||
using from './genres/fiori-service';
|
||||
|
||||
using from './common';
|
||||
using from '@capire/bookstore/srv/mashup';
|
||||
@@ -7,9 +7,19 @@
|
||||
"@capire/orders": "*",
|
||||
"@capire/common": "*",
|
||||
"@capire/data-viewer": "*",
|
||||
"@cap-js/hana": ">=1",
|
||||
"@sap-cloud-sdk/http-client": "^4",
|
||||
"@sap-cloud-sdk/resilience": "^4",
|
||||
"@sap/cds": ">=5",
|
||||
"express": "^4.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cap-js/sqlite": ">=1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cds-serve",
|
||||
"watch": "cds watch"
|
||||
},
|
||||
"cds": {
|
||||
"requires": {
|
||||
"ReviewsService": {
|
||||
@@ -20,15 +30,22 @@
|
||||
"kind": "odata",
|
||||
"model": "@capire/orders"
|
||||
},
|
||||
"messaging": {
|
||||
"[development]": { "kind": "file-based-messaging" },
|
||||
"[hybrid]": { "kind": "enterprise-messaging-shared" },
|
||||
"[production]": { "kind": "enterprise-messaging" }
|
||||
},
|
||||
"db": {
|
||||
"kind": "sql"
|
||||
"messaging": true,
|
||||
"db": true,
|
||||
"db-ext": {
|
||||
"[development]": {
|
||||
"model": "db/sqlite"
|
||||
},
|
||||
"[production]": {
|
||||
"model": "db/hana"
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": { "service": true }
|
||||
}
|
||||
}
|
||||
},
|
||||
"sapux": [
|
||||
"app/admin-authors",
|
||||
"app/admin-books",
|
||||
"app/browse"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,15 +1,2 @@
|
||||
const cds = require ('@sap/cds')
|
||||
|
||||
// Add mashup logic
|
||||
cds.once('served', require('./srv/mashup'))
|
||||
|
||||
// Add routes to UIs from imported packages
|
||||
cds.once('bootstrap',(app)=>{
|
||||
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
|
||||
app.serve ('/reviews') .from ('@capire/reviews','app/vue')
|
||||
app.serve ('/orders') .from('@capire/orders','app/orders')
|
||||
app.serve ('/data') .from('@capire/data-viewer','app/viewer')
|
||||
})
|
||||
|
||||
// Add Swagger UI
|
||||
require('./srv/swagger-ui')
|
||||
require('./srv/mashup')
|
||||
require('./srv/trees')
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Mashing up bookshop services with required services...
|
||||
//
|
||||
module.exports = async()=>{ // called by server.js
|
||||
const cds = require ('@sap/cds')
|
||||
|
||||
|
||||
// Add routes to UIs from imported packages
|
||||
if (!cds.env.production) cds.once ('bootstrap', (app) => {
|
||||
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
|
||||
app.serve ('/reviews') .from ('@capire/reviews','app/vue')
|
||||
app.serve ('/orders') .from('@capire/orders','app/orders')
|
||||
})
|
||||
|
||||
|
||||
// Mashing up bookshop services with required services...
|
||||
cds.once ('served', async ()=>{
|
||||
|
||||
const cds = require('@sap/cds')
|
||||
const CatalogService = await cds.connect.to ('CatalogService')
|
||||
const ReviewsService = await cds.connect.to ('ReviewsService')
|
||||
const OrdersService = await cds.connect.to ('OrdersService')
|
||||
@@ -18,7 +25,7 @@ module.exports = async()=>{ // called by server.js
|
||||
// Note: prepend is neccessary to intercept generic default handler
|
||||
//
|
||||
CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => {
|
||||
console.debug ('> delegating request to ReviewsService')
|
||||
console.debug ('> delegating request to ReviewsService') // eslint-disable-line no-console
|
||||
const [id] = req.params, { columns, limit } = req.query.SELECT
|
||||
return ReviewsService.read ('Reviews',columns).limit(limit).where({subject:String(id)})
|
||||
}))
|
||||
@@ -28,8 +35,8 @@ module.exports = async()=>{ // called by server.js
|
||||
//
|
||||
CatalogService.on ('OrderedBook', async (msg) => {
|
||||
const { book, quantity, buyer } = msg.data
|
||||
const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price })
|
||||
return OrdersService.tx(msg).create ('Orders').entries({
|
||||
const { title, price } = await db.read (Books, book, b => { b.title, b.price })
|
||||
return OrdersService.create ('OrdersNoDraft').entries({
|
||||
OrderNo: 'Order at '+ (new Date).toLocaleString(),
|
||||
Items: [{ product:{ID:`${book}`}, title, price, quantity }],
|
||||
buyer, createdBy: buyer
|
||||
@@ -40,7 +47,7 @@ module.exports = async()=>{ // called by server.js
|
||||
// Update Books' average ratings when ReviewsService signals updated reviews
|
||||
//
|
||||
ReviewsService.on ('reviewed', (msg) => {
|
||||
console.debug ('> received:', msg.event, msg.data)
|
||||
console.debug ('> received:', msg.event, msg.data) // eslint-disable-line no-console
|
||||
const { subject, count, rating } = msg.data
|
||||
return UPDATE(Books,subject).with({ numberOfReviews:count, rating })
|
||||
})
|
||||
@@ -49,10 +56,10 @@ module.exports = async()=>{ // called by server.js
|
||||
// Reduce stock of ordered books for orders are created from Orders admin UI
|
||||
//
|
||||
OrdersService.on ('OrderChanged', (msg) => {
|
||||
console.debug ('> received:', msg.event, msg.data)
|
||||
console.debug ('> received:', msg.event, msg.data) // eslint-disable-line no-console
|
||||
const { product, deltaQuantity } = msg.data
|
||||
return UPDATE (Books) .where ('ID =', product)
|
||||
.and ('stock >=', deltaQuantity)
|
||||
.set ('stock -=', deltaQuantity)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Adding Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
|
||||
const cds = require ('@sap/cds')
|
||||
try {
|
||||
const cds_swagger = require ('cds-swagger-ui-express')
|
||||
cds.once ('bootstrap', app => app.use (cds_swagger()) )
|
||||
} catch (err) {
|
||||
if (err.code !== 'MODULE_NOT_FOUND') throw err
|
||||
}
|
||||
50
bookstore/srv/trees.js
Normal file
50
bookstore/srv/trees.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const cds = require('@sap/cds/lib')
|
||||
|
||||
// PoC for simplified Fiori Tree Views
|
||||
cds.on('compile.for.runtime', csn => {
|
||||
for (let each of cds.linked(csn).definitions) {
|
||||
if (each.is_entity && each._service && each['@hierarchy']) _hierarchy (each)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const _hierarchy = entity => {
|
||||
|
||||
// Add annotations explaining the hierarchy structure to Fiori
|
||||
const Qualifier = entity.name.slice (entity._service.name.length+1) + 'Hierarchy'
|
||||
const parent = _parent4(entity)
|
||||
entity[`@Aggregation.RecursiveHierarchy#${Qualifier}.ParentNavigationProperty`] ??= {'=': parent.name }
|
||||
entity[`@Aggregation.RecursiveHierarchy#${Qualifier}.NodeProperty`] ??= {'=': parent.keys[0].ref[0] }
|
||||
|
||||
// Add expected hierarchy elements to the entity
|
||||
const columns = entity.projection.columns ??= ['*']
|
||||
const elements = entity.elements
|
||||
for (let e of Hierarchy.elements) {
|
||||
entity[`@Hierarchy.RecursiveHierarchy#${Qualifier}.${e.name}`] = {'=': e.name }
|
||||
if (e.name in elements) continue
|
||||
const { name, value, ...rest } = e
|
||||
elements[e.name] = Object.defineProperty ({ __proto__:e, ...rest }, 'parent', { value: entity })
|
||||
columns.push ({ ...value, as: name, cast: { type: e.type } })
|
||||
}
|
||||
|
||||
// Disable filter and sort for hierarchy elements
|
||||
entity['@Capabilities.FilterRestrictions.NonFilterableProperties'] =
|
||||
entity['@Capabilities.SortRestrictions.NonSortableProperties'] =
|
||||
Object.keys (Hierarchy.elements)
|
||||
}
|
||||
|
||||
|
||||
const _parent4 = entity => {
|
||||
const parent = entity['@hierarchy.parent'] || entity['@hierarchy.via']
|
||||
if (parent) return entity.elements [parent['=']||parent]
|
||||
else for (let e of entity.elements) // use first recursive uplink association
|
||||
if (e.is2one && e._target === entity) return e
|
||||
}
|
||||
|
||||
|
||||
const { Hierarchy } = cds.linked `aspect Hierarchy {
|
||||
LimitedDescendantCount : Int16 = null;
|
||||
DistanceFromRoot : Int16 = null;
|
||||
DrillState : String = null;
|
||||
LimitedRank : Int16 = null;
|
||||
}`.definitions
|
||||
@@ -12,11 +12,12 @@
|
||||
#
|
||||
|
||||
GET {{reviews-service}}/Reviews
|
||||
Authorization: Basic me:
|
||||
|
||||
###
|
||||
|
||||
POST {{reviews-service}}/Reviews
|
||||
Authorization: Basic {{$processEnv USER}}:
|
||||
Authorization: Basic me:
|
||||
Content-Type: application/json
|
||||
|
||||
{"subject":"201", "title":"boo", "rating":3 }
|
||||
@@ -41,7 +42,7 @@ GET {{bookshop}}/browse/Books(201)?
|
||||
###
|
||||
|
||||
GET {{bookshop}}/browse/Books?
|
||||
&$select=title,author&$expand=currency
|
||||
&$select=title,author&$expand=currency
|
||||
Accept-Language: de
|
||||
|
||||
#################################################
|
||||
@@ -50,23 +51,23 @@ Accept-Language: de
|
||||
#
|
||||
@newOrderID = e939604c-ab83-4d4f-bdb6-95fe30b3773e
|
||||
|
||||
GET {{bookshop}}/orders/Orders
|
||||
GET {{bookshop}}/odata/v4/orders/Orders
|
||||
|
||||
### Create order, still inactive
|
||||
POST {{bookshop}}/orders/Orders
|
||||
POST {{bookshop}}/odata/v4/orders/Orders
|
||||
Content-Type: application/json
|
||||
|
||||
{"ID": "{{newOrderID}}"}
|
||||
|
||||
### Get inactive order. We have to specify `IsActiveEntity`.
|
||||
GET {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)
|
||||
GET {{bookshop}}/odata/v4/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)
|
||||
|
||||
### Activate order using `.../<servicename>.draftActivate`
|
||||
POST {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)/OrdersService.draftActivate
|
||||
POST {{bookshop}}/odata/v4/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)/OrdersService.draftActivate
|
||||
Content-Type: application/json
|
||||
|
||||
### Get active order
|
||||
GET {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=true)
|
||||
GET {{bookshop}}/odata/v4/orders/Orders(ID={{newOrderID}},IsActiveEntity=true)
|
||||
|
||||
### Create author
|
||||
POST {{bookshop}}/admin/Authors
|
||||
|
||||
1
common/cds-plugin.js
Normal file
1
common/cds-plugin.js
Normal file
@@ -0,0 +1 @@
|
||||
// dummy to auto-load the plugin
|
||||
13
common/currencies.cds
Normal file
13
common/currencies.cds
Normal file
@@ -0,0 +1,13 @@
|
||||
using { sap } from '@sap/cds/common';
|
||||
|
||||
extend sap.common.Currencies with {
|
||||
// Currencies.code = ISO 4217 alphabetic three-letter code
|
||||
// with the first two letters being equal to ISO 3166 alphabetic country codes
|
||||
// See also:
|
||||
// [1] https://www.iso.org/iso-4217-currency-codes.html
|
||||
// [2] https://www.currency-iso.org/en/home/tables/table-a1.html
|
||||
// [3] https://www.ibm.com/support/knowledgecenter/en/SSZLC2_7.0.0/com.ibm.commerce.payments.developer.doc/refs/rpylerl2mst97.htm
|
||||
numcode : Integer;
|
||||
exponent : Integer; //> e.g. 2 --> 1 Dollar = 10^2 Cent
|
||||
minor : String; //> e.g. 'Cent'
|
||||
}
|
||||
@@ -1,45 +1,2 @@
|
||||
using { sap } from '@sap/cds/common';
|
||||
|
||||
extend sap.common.Currencies with {
|
||||
// Currencies.code = ISO 4217 alphabetic three-letter code
|
||||
// with the first two letters being equal to ISO 3166 alphabetic country codes
|
||||
// See also:
|
||||
// [1] https://www.iso.org/iso-4217-currency-codes.html
|
||||
// [2] https://www.currency-iso.org/en/home/tables/table-a1.html
|
||||
// [3] https://www.ibm.com/support/knowledgecenter/en/SSZLC2_7.0.0/com.ibm.commerce.payments.developer.doc/refs/rpylerl2mst97.htm
|
||||
numcode : Integer;
|
||||
exponent : Integer; //> e.g. 2 --> 1 Dollar = 10^2 Cent
|
||||
minor : String; //> e.g. 'Cent'
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The Code Lists below are designed as optional extensions to
|
||||
* the base schema. Switch them on by adding an Association to
|
||||
* one of the code list entities in your models or by:
|
||||
* annotate sap.common.Countries with @cds.persistence.skip:false;
|
||||
*/
|
||||
|
||||
context sap.common.countries {
|
||||
|
||||
extend sap.common.Countries {
|
||||
regions : Composition of many Regions on regions._parent = $self.code;
|
||||
}
|
||||
|
||||
entity Regions : sap.common.CodeList {
|
||||
key code : String(5); // ISO 3166-2 alpha5 codes, e.g. DE-BW
|
||||
children : Composition of many Regions on children._parent = $self.code;
|
||||
cities : Composition of many Cities on cities.region = $self;
|
||||
_parent : String(11);
|
||||
}
|
||||
entity Cities : sap.common.CodeList {
|
||||
key code : String(11);
|
||||
region : Association to Regions;
|
||||
districts : Composition of many Districts on districts.city = $self;
|
||||
}
|
||||
entity Districts : sap.common.CodeList {
|
||||
key code : String(11);
|
||||
city : Association to Cities;
|
||||
}
|
||||
|
||||
}
|
||||
using from './currencies';
|
||||
using from './regions';
|
||||
|
||||
@@ -4,5 +4,12 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@sap/cds": "*"
|
||||
},
|
||||
"cds": {
|
||||
"requires": {
|
||||
"@capire/common/data": {
|
||||
"model": "@capire/common"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
common/regions.cds
Normal file
22
common/regions.cds
Normal file
@@ -0,0 +1,22 @@
|
||||
using { sap.common } from '@sap/cds/common';
|
||||
namespace sap.common.countries;
|
||||
|
||||
extend common.Countries {
|
||||
regions : Composition of many Regions on regions._parent = $self.code;
|
||||
}
|
||||
|
||||
entity Regions : common.CodeList {
|
||||
key code : String(5); // ISO 3166-2 alpha5 codes, e.g. DE-BW
|
||||
children : Composition of many Regions on children._parent = $self.code;
|
||||
cities : Composition of many Cities on cities.region = $self;
|
||||
_parent : String(11);
|
||||
}
|
||||
entity Cities : common.CodeList {
|
||||
key code : String(11);
|
||||
region : Association to Regions;
|
||||
districts : Composition of many Districts on districts.city = $self;
|
||||
}
|
||||
entity Districts : common.CodeList {
|
||||
key code : String(11);
|
||||
city : Association to Cities;
|
||||
}
|
||||
20
eslint.config.mjs
Normal file
20
eslint.config.mjs
Normal file
@@ -0,0 +1,20 @@
|
||||
import cds from '@sap/cds/eslint.config.mjs'
|
||||
export default [ ...cds.recommended ]
|
||||
|
||||
//
|
||||
// The above is the recommended minimalistic eslint config, just using
|
||||
// recommended defaults provided by cds. Alternatively, go for enhanced
|
||||
// project-specific options, but only if really required, like this:
|
||||
//
|
||||
// export default [ ...cds.recommended, {
|
||||
// rules: {
|
||||
// 'complexity': [ 'warn', 66 ],
|
||||
// 'require-await': 'warn',
|
||||
// 'no-await-in-loop': 'warn',
|
||||
// },
|
||||
// }, {
|
||||
// ignores: [
|
||||
// '**/webapp/**'
|
||||
// ]
|
||||
// }]
|
||||
//
|
||||
@@ -1,195 +0,0 @@
|
||||
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="357px" height="283px" viewBox="-0.5 -0.5 357 283" content="<mxfile><diagram id="QQJxv4aCTC7ZgE7HHOvM" name="Page-1">7VrZcqM4FP0av7q0L4+T9DIvU9VVeZhnFtlQwRYly7HTX98XgzBgOpNJG7eZGlKVwNECuudwdSSyoI+b41cXldlfNjXFgqD0uKCfFoQIIuF3BbzWACO6BtYuT2sInYGn/LupQRzQfZ6aXYPVkLe28HnZBxO73ZrE97DIOXvoV1vZIu0BZbQ2vceogKckKsxFtb/z1Gc1qsKwKvxPk6+zcGcsmvHFUfK8dna/be63IBQxXpdtotBRc9NdFqX20IHo5wV9dNb6+mxzfDRFFdd+zL78pLR9aGe2/j0NSN3gJSr2zbgXDCVRmTtTNSIi2pQL+rCNd9UfuC6g24fYwdnad5EAxNY+7zJbhgLoJT5XPg3Zv4YYw+jL6jQzx2htt1ClNC7fGG/cGf0WIAjOwyHLvXkqo6RqdgDhAZb5TQFXGE5X+dEELVXXu2fjkyri6FRYFI+2sO50c7paGZEkVSXv7LPplKRSxwi1JYF+0g6hG98m5C/GeXPsQE28vxoLD+9eoUpTSiSrm4QXQ9L6+nCWmW7kkXUUphosaoS9bns+8wsnDcXjdNNJ6Pa2an73fKvEjPMdK874zfimktyMb3Ztvp15yQ2k1rtnO+VGpWyMbUViKsRUbFOB+2xjfTO2+bXZTuxmA8TdPdkrXv1UuN36Dl4fYyIQp+NWImDhRbyBCMS1RWBdatwc3vjIqNVofheJMvFqKrKxEr+NbHlJ9pATs03/qOwwXCVFtNvlST/EvZBeBgiaN/GXp/LI+d71INRfTsdbgd3ZvUtML2FBp2vjew7FpMGd/yT4neDykeAGzJki8vlL39OPRby5wzebw/OeuSW4P3lzRPp91MNpmnWN9kVPYmD7+KCnOgoXPZ0k0A78XapQ81aFuHdVwKu75Iq2B+knAKrJslNKqfyYZIhmS4ZFewzXDQgvO6VC0Kn0pOetJzJzPTH+NtHX0hN7W7bX01PY4pmroNjMBcWHgppET3ygJzaZnIJlu5rl3e3Lssj/d73/wvXecp2LyUj+GFD4BJ2A4tD9M2hwys1oVtJC0miyRaoeEHjDbSk8sg85pwkgCLA7A4QsNI8pYOgp5CA7T+QpyGCqueIkMLLVOXdJibtSFAaqJVKcICw1ZIuw+dAuRzQoQSKGKFeSsY+aVIyXRGMmtSJIEBk+jrRrKw2yJpprwTAYmek86shm6qz0FL6Z3q1JxVpAClKnL8MaCc4HmUMuYaUDVhXWuUroD267EFClolhKLhHSlA1uwtmSUsGY4lphitRkarr6ruwqty7/T5ubCyczovl3f3Qjt3Q3974rO0gA/5hL6IjbEb/V3Ei9FFIpyBuKcKF0f5KozA3lEqYQLDimUvyCuaGQSSFzYMkoHtwFvI1gkGGwokwJWHBdKXvA5fkfQOrq5/+woZ9/AA==</diagram></mxfile>" style="background-color: rgb(0, 68, 85);">
|
||||
<defs/>
|
||||
<g>
|
||||
<path d="M 199 202 L 249 202 L 269 242 L 249 282 L 199 282 L 179 242 Z" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 242px; margin-left: 180px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
@capire/
|
||||
<br/>
|
||||
<b>
|
||||
bookshop
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="224" y="246" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
@capire/...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 199 101 L 249 101 L 269 141 L 249 181 L 199 181 L 179 141 Z" fill="#f8cecc" stroke="#b85450" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 141px; margin-left: 180px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
@capire/
|
||||
<br/>
|
||||
<b>
|
||||
bookstore
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="224" y="145" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
@capire/...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 286 48 L 336 48 L 356 88 L 336 128 L 286 128 L 266 88 Z" fill="#d5e8d4" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 88px; margin-left: 267px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
@capire/
|
||||
<br/>
|
||||
<b>
|
||||
reviews
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="311" y="92" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
@capire/...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 286 153 L 336 153 L 356 193 L 336 233 L 286 233 L 266 193 Z" fill="#f5f5f5" stroke="#666666" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 193px; margin-left: 267px;">
|
||||
<div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
@capire/
|
||||
<br/>
|
||||
<b>
|
||||
common
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="311" y="197" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
@capire/...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 111 153 L 161 153 L 181 193 L 161 233 L 111 233 L 91 193 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 193px; margin-left: 92px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
@capire/
|
||||
<br/>
|
||||
<b>
|
||||
orders
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="136" y="197" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
@capire/...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 276.35 172.29 L 266.36 166.32" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 260.57 162.86 L 270.6 163.61 L 266.36 166.32 L 265.98 171.34 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 170.74 172.47 L 181.53 166.1" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 187.34 162.66 L 181.88 171.12 L 181.53 166.1 L 177.3 163.37 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 224 202 L 224 189.99" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 224 183.24 L 228.5 192.24 L 224 189.99 L 219.5 192.24 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 276.51 109.01 L 266.17 115.31" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 260.4 118.82 L 265.75 110.3 L 266.17 115.31 L 270.43 117.98 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 111 48 L 161 48 L 181 88 L 161 128 L 111 128 L 91 88 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 88px; margin-left: 92px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
@capire/
|
||||
<br/>
|
||||
<b>
|
||||
suppliers
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="136" y="92" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
@capire/...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 21 101 L 71 101 L 91 141 L 71 181 L 21 181 L 1 141 Z" fill="#e1d5e7" stroke="#9673a6" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 141px; margin-left: 2px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
<b>
|
||||
S/4
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="46" y="145" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
S/4
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 80.76 120.53 L 93.49 113.03" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 99.31 109.61 L 93.84 118.05 L 93.49 113.03 L 89.27 110.3 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 80.91 161.17 L 93.31 168.33" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 99.15 171.71 L 89.11 171.1 L 93.31 168.33 L 93.61 163.31 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 170.59 108.83 L 181.72 115.53" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 187.5 119.02 L 177.47 118.23 L 181.72 115.53 L 182.11 110.52 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 199 1 L 249 1 L 269 41 L 249 81 L 199 81 L 179 41 Z" fill="#e1d5e7" stroke="#9673a6" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 41px; margin-left: 180px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
@capire/
|
||||
<br/>
|
||||
<b>
|
||||
fiori
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="224" y="45" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
@capire/...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 224 101 L 224 89.99" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 224 83.24 L 228.5 92.24 L 224 89.99 L 219.5 92.24 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
</g>
|
||||
<switch>
|
||||
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
|
||||
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||
Text is not SVG - cannot display
|
||||
</text>
|
||||
</a>
|
||||
</switch>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,212 +0,0 @@
|
||||
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="368px" height="282px" viewBox="-0.5 -0.5 368 282" content="<mxfile><diagram id="QQJxv4aCTC7ZgE7HHOvM" name="Page-1">7Zpbc9o4FIB/DY9hdL88JqRJH9ppd9mZndk3YwvwxCBXmIT01+9xLNvYMaEl4ZIUeLB1JOt2viMdXXp0MFvduiCdfrWRSXoERaseve4RgpnU8Mglj4VEIF4IJi6OChGqBcP4p/FfltJlHJmFlxWizNoki9OmMLTzuQmzhixwzj40k41tEjUEaTAxzwTDMEieS/+No2xaSBWRtfyziSfTsmQsfINnQZnYt2QxDSL7sCain3p04KzNirfZamCSvPOa/XKzIbaqmDPz7Fc+mJDb2+X19NuX/778vEB/LX+gO3vhc7kPkqVvcI+IBPK7GuVVzh59P4gfy7yeV2M7zy4WT1q6hAQYp6Dpqzoe3ib588rau8XUpmVuUK1RGVeV4F5VxGWa+o6tsiDQx2n+OjWrYGLnkCo1Lp6ZzLha+r0UgQquHqZxZoZpEOafPQDDIJtmswRCOK9MvDIllUU4SQY2se6pPDoeGxGGIF9kzt6ZtZhI6hFCVUzJDvENvAlmcZKbxMDO4hAaMQzmC3h8HfoEZZm4DPtGoqceaGu+VKNxmVmtiTwJt8ZCg90jJPGxhPtPvFlSLYrwQw259uBO1/hWXhZ4s5pUOdfkwYuH7zdAJBtBXKTB/FWgvHlGowblmXVmHelm2i7wQVbUZQ3kNzGJDQUc30xUaLrNZKQ443syE+iCMJ5P/oFRiF7z/dgN0fK4dkP3bzevmwn+NvexgVl4fxPB0Lj7ODQf3IYiblTEumxIkREVYh829Hp7oaxlL0wd117YSw5PoWrop5nNTWcbs4XkOsiCo9NhMPAhu+jQQtLgndBBGT8uHfzUR9NvLjLuPJj+mYMpFidmLqLDXFqaNfPoMl+GQyhMgsUiDpuKanZzLoEvfCcW41ngska4pTA+yP/7U8/CLl1ots8oUM+JybavcEzU2HB4ruw1ZfIOZZYyZ5Igi++b2xRdGvYlfLcxtKxmCesmTKzc0ijzKBruP1vfV2jnRNqjuGzlVHTNs5yekKsavjuF8kxhNXO9MwoJl32uaPUjokkSlv21WEp3RJRI3GdYVD/JmsUQ0V+LFYIelF915rfaiPxg/MqXwXorfsXLZrJnfvWZ32of5mPxyw7DLzsuv+VR0+mu+obLNE3i88LvvPDrncYuGt58bvhqkxlCjdjJ8hMYNe48yRChMqPxRn7aeGznyTey45z3t/kRvOkvIHxkfMjZZaiNaKvPUE5Q78NpaDu95WS/Z6e3rMWhnIaug7czwpsS8pMiGANaEikOs5DUVJJy66tEizIgTyKGKFeSsR29XqxUn+j8CpQiSBApabMUwsCMiOZaMEw4P7DT23UQ9ufxi36R39NatmFJYYhV+aat1kjw1vQuSR+WapxjhrASmuyKr4ABFkvJJUK6OmuoCtF9SgVjimuFKVKHpffkD+puYuvifd9++9BLtfF4TDbcrRMjwfeyVDvMpSF87Mt2+HxwV4/p293v03JeCBd9wmE8Fk+neKp1+IZ1n2kYjyV4FAIcmJ237FBfMsEZlVIphHXLd4ElABIaE8byKyak5ePvPPxDsL4RXSSv75XTT/8D</diagram></mxfile>">
|
||||
<defs/>
|
||||
<g>
|
||||
<path d="M 207 201 L 257 201 L 277 241 L 257 281 L 207 281 L 187 241 Z" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 241px; margin-left: 188px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
<b style="font-size: 11px;">
|
||||
Bookshop
|
||||
</b>
|
||||
<br style="font-size: 11px;"/>
|
||||
App
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="232" y="244" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
||||
Bookshop...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 207 102 L 257 102 L 277 142 L 257 182 L 207 182 L 187 142 Z" fill="#f8cecc" stroke="#b85450" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 145px; margin-left: 188px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
<span style="font-size: 11px;">
|
||||
<span style="font-size: 11px;">
|
||||
<b>
|
||||
Bookstore
|
||||
<br/>
|
||||
</b>
|
||||
</span>
|
||||
App
|
||||
<br style="font-size: 11px;"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="232" y="148" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
||||
Bookstore...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 297 53 L 347 53 L 367 93 L 347 133 L 297 133 L 277 93 Z" fill="#d5e8d4" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 93px; margin-left: 278px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
<span style="font-size: 11px;">
|
||||
<b style="font-size: 11px;">
|
||||
Reviews
|
||||
</b>
|
||||
<br style="font-size: 11px;"/>
|
||||
Service
|
||||
<br style="font-size: 11px;"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="322" y="96" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
||||
Reviews...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 297 150 L 347 150 L 367 190 L 347 230 L 297 230 L 277 190 Z" fill="#e1d5e7" stroke="#9673a6" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 190px; margin-left: 278px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
<b>
|
||||
Common
|
||||
</b>
|
||||
<br/>
|
||||
Data
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="322" y="193" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
||||
Common...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 117 150 L 167 150 L 187 190 L 167 230 L 117 230 L 97 190 Z" fill="#d5e8d4" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 190px; margin-left: 98px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
<span style="font-size: 11px;">
|
||||
<b style="font-size: 11px;">
|
||||
Orders
|
||||
</b>
|
||||
<br style="font-size: 11px;"/>
|
||||
Service
|
||||
<br style="font-size: 11px;"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="142" y="193" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
||||
Orders...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 286.47 171.05 L 273.81 164.3" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 268.51 161.47 L 277.45 161.71 L 273.81 164.3 L 273.69 168.77 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 177.53 171.05 L 190.19 164.3" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 195.49 161.47 L 190.31 168.77 L 190.19 164.3 L 186.55 161.71 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 232 201 L 232 189.12" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 232 183.12 L 236 191.12 L 232 189.12 L 228 191.12 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 286.63 112.26 L 273.62 119.34" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 268.35 122.21 L 273.47 114.87 L 273.62 119.34 L 277.29 121.9 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 117 53 L 167 53 L 187 93 L 167 133 L 117 133 L 97 93 Z" fill="#d5e8d4" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 93px; margin-left: 98px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
<span style="font-size: 11px;">
|
||||
<b style="font-size: 11px;">
|
||||
Suppliers
|
||||
</b>
|
||||
<br style="font-size: 11px;"/>
|
||||
Service
|
||||
<br style="font-size: 11px;"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="142" y="96" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
||||
Suppliers...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 21 106 L 71 106 L 91 146 L 71 186 L 21 186 L 1 146 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 146px; margin-left: 2px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;">
|
||||
<span style="font-size: 11px;">
|
||||
S/4
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="46" y="149" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle" font-weight="bold">
|
||||
S/4
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 81.27 126.53 L 100.5 115.91" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 105.76 113.01 L 100.69 120.38 L 100.5 115.91 L 96.82 113.37 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 82.61 162.78 L 98.92 170.25" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 104.37 172.75 L 95.43 173.06 L 98.92 170.25 L 98.77 165.79 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 177.37 112.26 L 190.38 119.34" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 195.65 122.21 L 186.71 121.9 L 190.38 119.34 L 190.53 114.87 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 207 1 L 257 1 L 277 41 L 257 81 L 207 81 L 187 41 Z" fill="#fff2cc" stroke="#d6b656" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 44px; margin-left: 188px;">
|
||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
<span style="font-size: 11px;">
|
||||
<b style="font-size: 11px;">
|
||||
Fiori
|
||||
</b>
|
||||
<br style="font-size: 11px;"/>
|
||||
App
|
||||
<br style="font-size: 11px;"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="232" y="47" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
||||
Fiori...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 232 102 L 232 88.12" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 232 82.12 L 236 90.12 L 232 88.12 L 228 90.12 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
||||
</g>
|
||||
<switch>
|
||||
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
|
||||
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||
Text is not SVG - cannot display
|
||||
</text>
|
||||
</a>
|
||||
</switch>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 18 KiB |
29
event-mesh.json
Normal file
29
event-mesh.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"version": "1.1.0",
|
||||
"options": {
|
||||
"management": true,
|
||||
"messagingrest": true,
|
||||
"messaging": true
|
||||
},
|
||||
"rules": {
|
||||
"topicRules": {
|
||||
"publishFilter": [
|
||||
"*"
|
||||
],
|
||||
"subscribeFilter": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"queueRules": {
|
||||
"publishFilter": [
|
||||
"*"
|
||||
],
|
||||
"subscribeFilter": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"authorities": [
|
||||
"$ACCEPT_GRANTED_AUTHORITIES"
|
||||
]
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
Books = Books
|
||||
Book = Book
|
||||
ID = ID
|
||||
Title = Title
|
||||
Author = Author
|
||||
AuthorID = Author ID
|
||||
Stock = Stock
|
||||
Name = Name
|
||||
Description = Description
|
||||
Image = Image
|
||||
AuthorName = Author's Name
|
||||
DateOfBirth = Date of Birth
|
||||
DateOfDeath = Date of Death
|
||||
PlaceOfBirth = Place of Birth
|
||||
PlaceOfDeath = Place of Death
|
||||
Age = Age
|
||||
Lifetime = Lifetime
|
||||
Authors = Authors
|
||||
|
||||
Order = Order
|
||||
Orders = Orders
|
||||
OrderNo = Order Number
|
||||
OrderItems = Order Items
|
||||
Customer = Customer
|
||||
Product = Product
|
||||
ProductID = Product ID
|
||||
ProductTitle = Product Title
|
||||
UnitPrice = Unit Price
|
||||
Quantity = Quantity
|
||||
|
||||
Price = Price
|
||||
Currency = Currency
|
||||
Date = Date
|
||||
Rating = Rating
|
||||
NumberOfReviews = Number of Reviews
|
||||
|
||||
Genre = Genre
|
||||
Genres = Genres
|
||||
SubGenres = Sub Genres
|
||||
|
||||
NumCode = Numeric Code
|
||||
MinorUnit = Minor Unit
|
||||
Exponent = Exponent
|
||||
@@ -1,30 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Bookshop</title>
|
||||
|
||||
<script>
|
||||
window["sap-ushell-config"] = {
|
||||
defaultRenderer: "fiori2",
|
||||
applications: {}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
|
||||
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
|
||||
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
|
||||
data-sap-ui-compatVersion="edge"
|
||||
data-sap-ui-theme="sap_horizon"
|
||||
data-sap-ui-frameOptions="allow"
|
||||
></script>
|
||||
<script>
|
||||
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body class="sapUiBody" id="content"></body>
|
||||
</html>
|
||||
@@ -1,8 +0,0 @@
|
||||
using { sap.capire.bookshop } from '../../db/common';
|
||||
|
||||
annotate bookshop.GenreHierarchy {
|
||||
ID @sap.hierarchy.node.for;
|
||||
parent @sap.hierarchy.parent.node.for;
|
||||
hierarchyLevel @sap.hierarchy.level.for;
|
||||
drillState @sap.hierarchy.drill.state.for;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user