Compare commits
102 Commits
refactor/s
...
shared-db-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2c3e57e38 | ||
|
|
d79fbde2de | ||
|
|
a657be4adc | ||
|
|
d713b45596 | ||
|
|
e2901a9f60 | ||
|
|
1646ab5e13 | ||
|
|
8332be13e1 | ||
|
|
e22e1b0d12 | ||
|
|
70d7e6ffea | ||
|
|
89307140ad | ||
|
|
39335c8c9b | ||
|
|
dca837e595 | ||
|
|
7686931f56 | ||
|
|
e648b0072a | ||
|
|
49ca54ca82 | ||
|
|
24154725ba | ||
|
|
745698fe12 | ||
|
|
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 |
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
@@ -15,3 +15,6 @@ updates:
|
|||||||
- dependency-name: "chai"
|
- dependency-name: "chai"
|
||||||
# chai 5 doesn't work atm w/ cds.test, TODO fix that in cds.test
|
# chai 5 doesn't work atm w/ cds.test, TODO fix that in cds.test
|
||||||
versions: ["5.x"]
|
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"]
|
||||||
|
|||||||
4
.github/workflows/node.js.yml
vendored
4
.github/workflows/node.js.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [20.x, 18.x]
|
node-version: [22.x, 20.x, 18.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -33,6 +33,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 22.x
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run lint
|
- run: npm run lint
|
||||||
|
|||||||
@@ -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
|
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.
|
parties the right to use of access any SAP External Product, through API Calls.
|
||||||
|
|
||||||
Files: *
|
Files: *.*
|
||||||
Copyright: 2019-2020 SAP SE or an SAP affiliate company and cap-cloud-samples
|
Copyright: 2019-2025 SAP SE or an SAP affiliate company and cap-cloud-samples
|
||||||
License: Apache-2.0
|
License: Apache-2.0
|
||||||
|
|||||||
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@@ -13,13 +13,5 @@
|
|||||||
"**/cds/lib/req/cds-context.js",
|
"**/cds/lib/req/cds-context.js",
|
||||||
"**/odata-v4/okra/**"
|
"**/odata-v4/okra/**"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"eslint.probe": [
|
|
||||||
"cds",
|
|
||||||
"csn",
|
|
||||||
"csv",
|
|
||||||
"csv (semicolon)",
|
|
||||||
"tsv",
|
|
||||||
"tab"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
# Welcome to cap/samples
|
# 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).
|
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).
|
||||||
@@ -7,20 +9,12 @@ Find here a collection of samples for the [SAP Cloud Application Programming Mod
|
|||||||

|

|
||||||
|
|
||||||

|

|
||||||
[](https://api.reuse.software/info/github.com/SAP-samples/cloud-cap-samples)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Preliminaries
|
### Preliminaries
|
||||||
|
|
||||||
1. Ensure you have the latest LTS version of Node.js installed (see [Getting Started](https://cap.cloud.sap/docs/get-started/))
|
Ensure you have the latest LTS version of Node.js, [`@sap/cds-dk`](https://www.npmjs.com/package/@sap/cds-dk) installed globally, `git` and your IDE ready (see [Initial Setup](https://cap.cloud.sap/docs/get-started/#setup))
|
||||||
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
|
### Download
|
||||||
|
|
||||||
@@ -36,7 +30,7 @@ cd samples
|
|||||||
In the samples folder run:
|
In the samples folder run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install
|
npm ci
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run
|
### Run
|
||||||
|
|||||||
1
app/router/bookshop
Symbolic link
1
app/router/bookshop
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../bookshop/app/vue
|
||||||
1
app/router/orders
Symbolic link
1
app/router/orders
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../orders/app/orders
|
||||||
2427
app/router/package-lock.json
generated
Normal file
2427
app/router/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
app/router/package.json
Normal file
12
app/router/package.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "approuter",
|
||||||
|
"dependencies": {
|
||||||
|
"@sap/approuter": "^19.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node node_modules/@sap/approuter/approuter.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
app/router/reviews
Symbolic link
1
app/router/reviews
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../reviews/app/vue
|
||||||
52
app/router/xs-app.json
Normal file
52
app/router/xs-app.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"welcomeFile": "app/bookshop/index.html",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"source": "^/app/(.*)$",
|
||||||
|
"target": "$1",
|
||||||
|
"localDir": ".",
|
||||||
|
"cacheControl": "no-cache, no-store, must-revalidate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "^/appconfig/",
|
||||||
|
"localDir": ".",
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "^(.*)$",
|
||||||
|
"target": "$1",
|
||||||
|
"localDir": ".",
|
||||||
|
"cacheControl": "no-cache, no-store, must-revalidate"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ const books = Vue.createApp ({
|
|||||||
search: ({target:{value:v}}) => books.fetch(v && '&$search='+v),
|
search: ({target:{value:v}}) => books.fetch(v && '&$search='+v),
|
||||||
|
|
||||||
async fetch (etc='') {
|
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
|
books.list = data.value
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ function csrfToken (request) {
|
|||||||
document.csrfToken = token
|
document.csrfToken = token
|
||||||
request.headers['x-csrf-token'] = document.csrfToken
|
request.headers['x-csrf-token'] = document.csrfToken
|
||||||
return request
|
return request
|
||||||
}).catch(_ => {
|
}).catch(() => {
|
||||||
document.csrfToken = null // set mark to not try again
|
document.csrfToken = null // set mark to not try again
|
||||||
return request
|
return request
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
const cds = require('@sap/cds')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In order to keep basic bookshop sample as simple as possible, we don't add
|
* 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
|
* 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
|
// NOTE: We use cds.on('served') to delay the UPSERTs after the db init
|
||||||
// to run after all INSERTs from .csv files happened.
|
// 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 (
|
UPSERT.into ('sap.common.Currencies') .columns (
|
||||||
[ 'code', 'symbol', 'name' ]
|
[ 'code', 'symbol', 'name' ]
|
||||||
) .rows (
|
) .rows (
|
||||||
@@ -16,4 +18,4 @@ module.exports = cds.on('served', ()=> cds.run(
|
|||||||
[ 'ILS', '₪', 'Shekel' ],
|
[ 'ILS', '₪', 'Shekel' ],
|
||||||
[ 'JPY', '¥', 'Yen' ],
|
[ 'JPY', '¥', 'Yen' ],
|
||||||
)
|
)
|
||||||
))
|
)
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
const { CatalogService } = require('./srv/cat-service')
|
|
||||||
module.exports = { CatalogService }
|
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds')
|
||||||
|
|
||||||
module.exports = class AdminService extends cds.ApplicationService { init(){
|
module.exports = class AdminService extends cds.ApplicationService { init(){
|
||||||
this.before ('NEW','Authors', genid)
|
this.before (['NEW','CREATE'],'Authors', genid)
|
||||||
this.before ('NEW','Books', genid)
|
this.before (['NEW','CREATE'],'Books', genid)
|
||||||
return super.init()
|
return super.init()
|
||||||
}}
|
}}
|
||||||
|
|
||||||
/** Generate primary keys for target entity in request */
|
/** Generate primary keys for target entity in request */
|
||||||
async function genid (req) {
|
async function genid (req) {
|
||||||
|
if (req.data.ID) return
|
||||||
const {id} = await SELECT.one.from(req.target).columns('max(ID) as id')
|
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 { Books } = cds.entities('sap.capire.bookshop')
|
||||||
const { ListOfBooks } = this.entities
|
const { ListOfBooks } = this.entities
|
||||||
@@ -32,3 +34,5 @@ module.exports = class CatalogService extends cds.ApplicationService { init() {
|
|||||||
// Delegate requests to the underlying generic service
|
// Delegate requests to the underlying generic service
|
||||||
return super.init()
|
return super.init()
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
module.exports = CatalogService
|
||||||
|
|||||||
@@ -3,15 +3,15 @@
|
|||||||
# Genres
|
# Genres
|
||||||
#
|
#
|
||||||
|
|
||||||
GET http://localhost:4004/test/Genres?
|
GET http://localhost:4004/odata/v4/test/Genres?
|
||||||
###
|
###
|
||||||
|
|
||||||
GET http://localhost:4004/test/Genres?
|
GET http://localhost:4004/odata/v4/test/Genres?
|
||||||
&$filter=parent_ID eq null&$select=name
|
&$filter=parent_ID eq null&$select=name
|
||||||
&$expand=children($select=name)
|
&$expand=children($select=name)
|
||||||
###
|
###
|
||||||
|
|
||||||
POST http://localhost:4004/test/Genres?
|
POST http://localhost:4004/odata/v4/test/Genres?
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{ "ID":100, "name":"Some Sample Genres...", "children":[
|
{ "ID":100, "name":"Some Sample Genres...", "children":[
|
||||||
@@ -26,13 +26,13 @@ Content-Type: application/json
|
|||||||
]}
|
]}
|
||||||
###
|
###
|
||||||
|
|
||||||
GET http://localhost:4004/test/Genres(100)?
|
GET http://localhost:4004/odata/v4/test/Genres(100)?
|
||||||
# &$expand=children
|
# &$expand=children
|
||||||
# &$expand=children($expand=children($expand=children($expand=children)))
|
# &$expand=children($expand=children($expand=children($expand=children)))
|
||||||
###
|
###
|
||||||
|
|
||||||
DELETE http://localhost:4004/test/Genres(103)
|
DELETE http://localhost:4004/odata/v4/test/Genres(103)
|
||||||
###
|
###
|
||||||
|
|
||||||
DELETE http://localhost:4004/test/Genres(100)
|
DELETE http://localhost:4004/odata/v4/test/Genres(100)
|
||||||
###
|
###
|
||||||
|
|||||||
12
bookstore/_i18n/i18n_de.properties
Normal file
12
bookstore/_i18n/i18n_de.properties
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Books = Bücher
|
||||||
|
Book = Buch
|
||||||
|
Title = Titel
|
||||||
|
Description = Beschreibung
|
||||||
|
Stock = Bestand
|
||||||
|
Image = Bild
|
||||||
|
Price = Preis
|
||||||
|
Currency = Währung
|
||||||
|
|
||||||
|
Authors = Autoren
|
||||||
|
Author = Autor
|
||||||
|
AuthorID = ID des Autors
|
||||||
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
|
||||||
@@ -2,14 +2,21 @@
|
|||||||
"name": "@capire/bookstore",
|
"name": "@capire/bookstore",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cap-js/hana": "^1.7.0",
|
||||||
"@capire/bookshop": "*",
|
"@capire/bookshop": "*",
|
||||||
"@capire/reviews": "*",
|
|
||||||
"@capire/orders": "*",
|
|
||||||
"@capire/common": "*",
|
"@capire/common": "*",
|
||||||
"@capire/data-viewer": "*",
|
"@capire/data-viewer": "*",
|
||||||
|
"@capire/orders": "*",
|
||||||
|
"@capire/reviews": "*",
|
||||||
|
"@sap-cloud-sdk/http-client": "^3.26.4",
|
||||||
|
"@sap-cloud-sdk/resilience": "^3.26.4",
|
||||||
"@sap/cds": ">=5",
|
"@sap/cds": ">=5",
|
||||||
|
"@sap/xssec": "^4.4.0",
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "cds-serve"
|
||||||
|
},
|
||||||
"cds": {
|
"cds": {
|
||||||
"requires": {
|
"requires": {
|
||||||
"ReviewsService": {
|
"ReviewsService": {
|
||||||
@@ -20,15 +27,13 @@
|
|||||||
"kind": "odata",
|
"kind": "odata",
|
||||||
"model": "@capire/orders"
|
"model": "@capire/orders"
|
||||||
},
|
},
|
||||||
"messaging": {
|
"messaging": true,
|
||||||
"[development]": { "kind": "file-based-messaging" },
|
|
||||||
"[hybrid]": { "kind": "enterprise-messaging-shared" },
|
|
||||||
"[production]": { "kind": "enterprise-messaging" }
|
|
||||||
},
|
|
||||||
"db": {
|
"db": {
|
||||||
"kind": "sql"
|
"kind": "sql"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"log": { "service": true }
|
"log": {
|
||||||
|
"service": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,10 +5,15 @@ cds.once('served', require('./srv/mashup'))
|
|||||||
|
|
||||||
// Add routes to UIs from imported packages
|
// Add routes to UIs from imported packages
|
||||||
cds.once('bootstrap',(app)=>{
|
cds.once('bootstrap',(app)=>{
|
||||||
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
|
try {
|
||||||
app.serve ('/reviews') .from ('@capire/reviews','app/vue')
|
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
|
||||||
app.serve ('/orders') .from('@capire/orders','app/orders')
|
app.serve ('/reviews') .from ('@capire/reviews','app/vue')
|
||||||
app.serve ('/data') .from('@capire/data-viewer','app/viewer')
|
app.serve ('/orders') .from('@capire/orders','app/orders')
|
||||||
|
app.serve ('/data') .from('@capire/data-viewer','app/viewer')
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'MODULE_NOT_FOUND') throw new Error('Run "npm ci" to install the required dependencies', { cause: err })
|
||||||
|
throw err
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add Swagger UI
|
// Add Swagger UI
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ module.exports = async()=>{ // called by server.js
|
|||||||
// Note: prepend is neccessary to intercept generic default handler
|
// Note: prepend is neccessary to intercept generic default handler
|
||||||
//
|
//
|
||||||
CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => {
|
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
|
const [id] = req.params, { columns, limit } = req.query.SELECT
|
||||||
return ReviewsService.read ('Reviews',columns).limit(limit).where({subject:String(id)})
|
return ReviewsService.read ('Reviews',columns).limit(limit).where({subject:String(id)})
|
||||||
}))
|
}))
|
||||||
@@ -28,8 +28,8 @@ module.exports = async()=>{ // called by server.js
|
|||||||
//
|
//
|
||||||
CatalogService.on ('OrderedBook', async (msg) => {
|
CatalogService.on ('OrderedBook', async (msg) => {
|
||||||
const { book, quantity, buyer } = msg.data
|
const { book, quantity, buyer } = msg.data
|
||||||
const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price })
|
const { title, price } = await db.read (Books, book, b => { b.title, b.price })
|
||||||
return OrdersService.tx(msg).create ('Orders').entries({
|
return OrdersService.create ('OrdersNoDraft').entries({
|
||||||
OrderNo: 'Order at '+ (new Date).toLocaleString(),
|
OrderNo: 'Order at '+ (new Date).toLocaleString(),
|
||||||
Items: [{ product:{ID:`${book}`}, title, price, quantity }],
|
Items: [{ product:{ID:`${book}`}, title, price, quantity }],
|
||||||
buyer, createdBy: buyer
|
buyer, createdBy: buyer
|
||||||
@@ -40,7 +40,7 @@ module.exports = async()=>{ // called by server.js
|
|||||||
// Update Books' average ratings when ReviewsService signals updated reviews
|
// Update Books' average ratings when ReviewsService signals updated reviews
|
||||||
//
|
//
|
||||||
ReviewsService.on ('reviewed', (msg) => {
|
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
|
const { subject, count, rating } = msg.data
|
||||||
return UPDATE(Books,subject).with({ numberOfReviews:count, rating })
|
return UPDATE(Books,subject).with({ numberOfReviews:count, rating })
|
||||||
})
|
})
|
||||||
@@ -49,7 +49,7 @@ module.exports = async()=>{ // called by server.js
|
|||||||
// Reduce stock of ordered books for orders are created from Orders admin UI
|
// Reduce stock of ordered books for orders are created from Orders admin UI
|
||||||
//
|
//
|
||||||
OrdersService.on ('OrderChanged', (msg) => {
|
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
|
const { product, deltaQuantity } = msg.data
|
||||||
return UPDATE (Books) .where ('ID =', product)
|
return UPDATE (Books) .where ('ID =', product)
|
||||||
.and ('stock >=', deltaQuantity)
|
.and ('stock >=', deltaQuantity)
|
||||||
|
|||||||
@@ -12,11 +12,12 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
GET {{reviews-service}}/Reviews
|
GET {{reviews-service}}/Reviews
|
||||||
|
Authorization: Basic me:
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
POST {{reviews-service}}/Reviews
|
POST {{reviews-service}}/Reviews
|
||||||
Authorization: Basic {{$processEnv USER}}:
|
Authorization: Basic me:
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{"subject":"201", "title":"boo", "rating":3 }
|
{"subject":"201", "title":"boo", "rating":3 }
|
||||||
@@ -41,7 +42,7 @@ GET {{bookshop}}/browse/Books(201)?
|
|||||||
###
|
###
|
||||||
|
|
||||||
GET {{bookshop}}/browse/Books?
|
GET {{bookshop}}/browse/Books?
|
||||||
&$select=title,author&$expand=currency
|
&$select=title,author&$expand=currency
|
||||||
Accept-Language: de
|
Accept-Language: de
|
||||||
|
|
||||||
#################################################
|
#################################################
|
||||||
@@ -50,23 +51,23 @@ Accept-Language: de
|
|||||||
#
|
#
|
||||||
@newOrderID = e939604c-ab83-4d4f-bdb6-95fe30b3773e
|
@newOrderID = e939604c-ab83-4d4f-bdb6-95fe30b3773e
|
||||||
|
|
||||||
GET {{bookshop}}/orders/Orders
|
GET {{bookshop}}/odata/v4/orders/Orders
|
||||||
|
|
||||||
### Create order, still inactive
|
### Create order, still inactive
|
||||||
POST {{bookshop}}/orders/Orders
|
POST {{bookshop}}/odata/v4/orders/Orders
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{"ID": "{{newOrderID}}"}
|
{"ID": "{{newOrderID}}"}
|
||||||
|
|
||||||
### Get inactive order. We have to specify `IsActiveEntity`.
|
### 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`
|
### 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
|
Content-Type: application/json
|
||||||
|
|
||||||
### Get active order
|
### Get active order
|
||||||
GET {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=true)
|
GET {{bookshop}}/odata/v4/orders/Orders(ID={{newOrderID}},IsActiveEntity=true)
|
||||||
|
|
||||||
### Create author
|
### Create author
|
||||||
POST {{bookshop}}/admin/Authors
|
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
|
||||||
@@ -4,5 +4,12 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": "*"
|
"@sap/cds": "*"
|
||||||
|
},
|
||||||
|
"cds": {
|
||||||
|
"requires": {
|
||||||
|
"@capire/common/data": {
|
||||||
|
"model": "@capire/common"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
const cds = require('@sap/eslint-plugin-cds')
|
|
||||||
const globals = require('globals')
|
|
||||||
const js = require('@eslint/js')
|
|
||||||
|
|
||||||
module.exports = [
|
|
||||||
cds.configs.recommended,
|
|
||||||
js.configs.recommended,
|
|
||||||
{
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
es2022: true,
|
|
||||||
...globals.browser,
|
|
||||||
...globals.node,
|
|
||||||
...globals.jest,
|
|
||||||
...globals.mocha,
|
|
||||||
cds: true,
|
|
||||||
sap: true,
|
|
||||||
CDL: true,
|
|
||||||
CQL: true,
|
|
||||||
CREATE: true,
|
|
||||||
DELETE: true,
|
|
||||||
DROP: true,
|
|
||||||
INSERT: true,
|
|
||||||
SELECT: true,
|
|
||||||
UPDATE: true,
|
|
||||||
UPSERT: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'no-console': 'off',
|
|
||||||
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
||||||
'require-atomic-updates': 'off',
|
|
||||||
'require-await': 'warn'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
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/**'
|
||||||
|
// ]
|
||||||
|
// }]
|
||||||
|
//
|
||||||
@@ -18,7 +18,7 @@ class DataService extends cds.ApplicationService { init(){
|
|||||||
.sort((e1, e2) => e1.name.localeCompare(e2.name))
|
.sort((e1, e2) => e1.name.localeCompare(e2.name))
|
||||||
.map(e => {
|
.map(e => {
|
||||||
const columns = Object.entries(e.elements)
|
const columns = Object.entries(e.elements)
|
||||||
.filter(([_, el]) => !(el instanceof cds.Association)) // exclude assocs+compositions
|
.filter(([,el]) => !(el instanceof cds.Association)) // exclude assocs+compositions
|
||||||
.map(([name, el]) => { return { name, type: el.type, isKey:!!el.key }})
|
.map(([name, el]) => { return { name, type: el.type, isKey:!!el.key }})
|
||||||
return { name: e.name, columns }
|
return { name: e.name, columns }
|
||||||
})
|
})
|
||||||
@@ -34,7 +34,8 @@ class DataService extends cds.ApplicationService { init(){
|
|||||||
query.SELECT.limit = req.query.SELECT.limit // forward $skip / $top
|
query.SELECT.limit = req.query.SELECT.limit // forward $skip / $top
|
||||||
|
|
||||||
const dataSource = findDataSource(dataSourceName, entityName)
|
const dataSource = findDataSource(dataSourceName, entityName)
|
||||||
const res = await dataSource.run(query)
|
let res = await dataSource.run(query)
|
||||||
|
if (!Array.isArray(res)) res = [res] // singleton result
|
||||||
return res.map((line) => {
|
return res.map((line) => {
|
||||||
const record = Object.entries(line).map(([column, data]) => ({ column, data }))
|
const record = Object.entries(line).map(([column, data]) => ({ column, data }))
|
||||||
return {
|
return {
|
||||||
@@ -49,6 +50,7 @@ class DataService extends cds.ApplicationService { init(){
|
|||||||
|
|
||||||
module.exports = { DataService }
|
module.exports = { DataService }
|
||||||
|
|
||||||
|
/** @returns {cds.Service} */
|
||||||
function findDataSource(dataSourceName, entityName) {
|
function findDataSource(dataSourceName, entityName) {
|
||||||
for (let srv of Object.values(cds.services)) { // all connected services
|
for (let srv of Object.values(cds.services)) { // all connected services
|
||||||
if (!srv.name) continue // FIXME intermediate/pending in cds.services ?
|
if (!srv.name) continue // FIXME intermediate/pending in cds.services ?
|
||||||
@@ -13,10 +13,5 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cds-serve",
|
"start": "cds-serve",
|
||||||
"watch": "cds watch"
|
"watch": "cds watch"
|
||||||
},
|
|
||||||
"cds": {
|
|
||||||
"requires": {
|
|
||||||
"db": "sql"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
const cds = require ('@sap/cds/lib')
|
const cds = require ('@sap/cds')
|
||||||
const LOG = cds.log('cds.log')
|
const LOG = cds.log('cds.log')
|
||||||
|
|
||||||
module.exports = class LogService extends cds.Service {
|
module.exports = class LogService extends cds.Service {
|
||||||
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,14 +1,5 @@
|
|||||||
Books = Bücher
|
|
||||||
Book = Buch
|
|
||||||
ID = ID
|
|
||||||
Title = Titel
|
|
||||||
Authors = Autoren
|
|
||||||
Author = Autor
|
|
||||||
AuthorID = ID des Autors
|
|
||||||
AuthorName = Name des Autors
|
AuthorName = Name des Autors
|
||||||
Age = Alter
|
Age = Alter
|
||||||
Name = Name
|
rder = Bestellung
|
||||||
Stock = Bestand
|
|
||||||
Order = Bestellung
|
|
||||||
Orders = Bestellungen
|
Orders = Bestellungen
|
||||||
Price = Preis
|
Price = Preis
|
||||||
|
|||||||
8
fiori/app/_i18n/i18n_en.properties
Normal file
8
fiori/app/_i18n/i18n_en.properties
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Age = Age
|
||||||
|
Lifetime = Lifetime
|
||||||
|
|
||||||
|
SubGenres = Sub Genres
|
||||||
|
|
||||||
|
NumCode = Numeric Code
|
||||||
|
MinorUnit = Minor Unit
|
||||||
|
Exponent = Exponent
|
||||||
@@ -6,8 +6,8 @@ using CatalogService from '@capire/bookstore';
|
|||||||
//
|
//
|
||||||
annotate CatalogService.Books with @(UI : {
|
annotate CatalogService.Books with @(UI : {
|
||||||
HeaderInfo : {
|
HeaderInfo : {
|
||||||
TypeName : 'Book',
|
TypeName : '{i18n>Book}',
|
||||||
TypeNamePlural : 'Books',
|
TypeNamePlural : '{i18n>Books}',
|
||||||
Description : {Value : author}
|
Description : {Value : author}
|
||||||
},
|
},
|
||||||
HeaderFacets : [{
|
HeaderFacets : [{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
|
||||||
|
|
||||||
|
<head>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
|
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
@@ -10,21 +10,22 @@
|
|||||||
<script>
|
<script>
|
||||||
window["sap-ushell-config"] = {
|
window["sap-ushell-config"] = {
|
||||||
defaultRenderer: "fiori2",
|
defaultRenderer: "fiori2",
|
||||||
applications: {}
|
applications: {},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></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://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
|
<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-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout" data-sap-ui-compatVersion="edge"
|
||||||
data-sap-ui-compatVersion="edge"
|
data-sap-ui-theme="sap_horizon"></script>
|
||||||
data-sap-ui-theme="sap_horizon"
|
|
||||||
data-sap-ui-frameOptions="allow"
|
|
||||||
></script>
|
|
||||||
<script>
|
<script>
|
||||||
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
|
sap.ui.getCore().attachInit(() =>
|
||||||
|
sap.ushell.Container.createRenderer().placeAt("content")
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body class="sapUiBody" id="content"></body>
|
|
||||||
|
<body class="sapUiBody" id="content">
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// install OData v2 adapter
|
|
||||||
const cds = require("@sap/cds")
|
|
||||||
const proxy = require('@cap-js-community/odata-v2-adapter')
|
|
||||||
const opts = global.it ? { target:'auto' } : {} // for tests, set 'auto' to detect port dynamically
|
|
||||||
cds.on('bootstrap', app => app.use(proxy(opts))) // install proxy
|
|
||||||
// cds.log('cov2ap','silent') // suppress anoying log outpout, e.g. for `npm run mocha -- --reporter nyan`
|
|
||||||
|
|
||||||
module.exports = require('@capire/bookstore/server.js')
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# Hello World Getting Started Sample
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
- To run the JavaScript implementation, open a new terminal and run `cds watch`.
|
|
||||||
- To run the TypeScript implementation, open a new terminal and run `cds-ts watch`.
|
|
||||||
|
|
||||||
Then call the service at: http://localhost:4004/say/hello(to='world')
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
Learn more about:
|
|
||||||
|
|
||||||
- [Hello World!](https://cap.cloud.sap/docs/get-started/hello-world)
|
|
||||||
- [Using TypeScript](https://cap.cloud.sap/docs/node.js/typescript)
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@capire/hello-world",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"scripts": {
|
|
||||||
"test": "npx jest --silent",
|
|
||||||
"start": "cds serve srv/world.cds",
|
|
||||||
"start:ts": "cds-ts serve srv/world.cds"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@sap/cds": ">=5.0.4"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/jest": "*",
|
|
||||||
"@types/node": "*",
|
|
||||||
"typescript": ">=4.3.5"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"extends": "eslint:recommended",
|
|
||||||
"env": {
|
|
||||||
"es2020": true,
|
|
||||||
"node": true,
|
|
||||||
"jest": true,
|
|
||||||
"mocha": true
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"SELECT": true,
|
|
||||||
"INSERT": true,
|
|
||||||
"UPDATE": true,
|
|
||||||
"DELETE": true,
|
|
||||||
"CREATE": true,
|
|
||||||
"DROP": true,
|
|
||||||
"CDL": true,
|
|
||||||
"CQL": true,
|
|
||||||
"CXL": true,
|
|
||||||
"cds": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"no-console": "off",
|
|
||||||
"require-atomic-updates": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
service say @(path: '/say') {
|
|
||||||
function hello (to:String) returns String;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
module.exports = class say {
|
|
||||||
hello(req) {
|
|
||||||
let {to} = req.data
|
|
||||||
if (to === 'me') to = require('os').userInfo().username
|
|
||||||
return `Hello ${to}!`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { Request } from "@sap/cds"
|
|
||||||
|
|
||||||
module.exports = class say {
|
|
||||||
hello(req: Request) {
|
|
||||||
return `Hello ${req.data.to} from a TypeScript file!`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
const cds = require ('@sap/cds')
|
|
||||||
describe('Hello world!', () => {
|
|
||||||
|
|
||||||
beforeAll (()=> process.env.CDS_TYPESCRIPT = true)
|
|
||||||
afterAll (()=> delete process.env.CDS_TYPESCRIPT)
|
|
||||||
const {GET} = cds.test.in(__dirname,'../srv').run('serve', 'world.cds')
|
|
||||||
|
|
||||||
it('should say hello with class impl', async () => {
|
|
||||||
const {data} = await GET`/say/hello(to='world')`
|
|
||||||
expect(data.value).toMatch(/Hello world.*typescript.*/i)
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
GET http://localhost:4004/say/hello(to='world')
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace sap.capire.media;
|
|
||||||
|
|
||||||
entity Media {
|
|
||||||
|
|
||||||
key id:Integer;
|
|
||||||
@Core.MediaType: mediaType
|
|
||||||
content : LargeBinary ;
|
|
||||||
|
|
||||||
@Core.IsMediaType: true
|
|
||||||
mediaType : String;
|
|
||||||
fileName : String;
|
|
||||||
applicationName:String;
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
using from './db/data-model';
|
|
||||||
using from './srv/media-service';
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@capire/media",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"dependencies": {
|
|
||||||
"lokijs": "^1.5.6"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"db",
|
|
||||||
"srv",
|
|
||||||
"index.cds"
|
|
||||||
],
|
|
||||||
"cds": {
|
|
||||||
"requires": {
|
|
||||||
"db": {
|
|
||||||
"kind": "sql"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
using { sap.capire.media as db } from '../db/data-model';
|
|
||||||
namespace sap.capire.media;
|
|
||||||
|
|
||||||
service MediaServer {
|
|
||||||
entity Media as projection on db.Media ;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
const loki = require('lokijs')
|
|
||||||
const db = new loki('DB')
|
|
||||||
const mediaDB = db.addCollection('Media')
|
|
||||||
const { Readable, PassThrough } = require('stream')
|
|
||||||
|
|
||||||
module.exports = srv => {
|
|
||||||
srv.before('CREATE', 'Media', req => {
|
|
||||||
const obj = mediaDB.insert({ media: '' })
|
|
||||||
req.data.id = obj.$loki
|
|
||||||
})
|
|
||||||
|
|
||||||
srv.on('UPDATE', 'Media', (req, next) => {
|
|
||||||
const url = req._.req.path
|
|
||||||
if (url.includes('content')) {
|
|
||||||
const id = req.data.id
|
|
||||||
const obj = mediaDB.get(id)
|
|
||||||
if (!obj) {
|
|
||||||
req.reject(404, 'No record found for the ID')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const stream = new PassThrough()
|
|
||||||
const chunks = []
|
|
||||||
stream.on('data', chunk => {
|
|
||||||
chunks.push(chunk)
|
|
||||||
})
|
|
||||||
stream.on('end', () => {
|
|
||||||
obj.media = Buffer.concat(chunks).toString('base64')
|
|
||||||
mediaDB.update(obj)
|
|
||||||
})
|
|
||||||
req.data.content.pipe(stream)
|
|
||||||
} else return next()
|
|
||||||
})
|
|
||||||
|
|
||||||
srv.on('READ', 'Media', (req, next) => {
|
|
||||||
const url = req._.req.path
|
|
||||||
if (url.includes('content')) {
|
|
||||||
const id = req.data.id
|
|
||||||
const mediaObj = mediaDB.get(id)
|
|
||||||
if (!mediaObj) {
|
|
||||||
req.reject(404, 'Media not found for the ID')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const decodedMedia = Buffer.from(
|
|
||||||
mediaObj.media.split(';base64,').pop(),
|
|
||||||
'base64'
|
|
||||||
)
|
|
||||||
return _formatResult(decodedMedia)
|
|
||||||
} else return next() //> delegate to next/default handlers
|
|
||||||
})
|
|
||||||
|
|
||||||
srv.on('DELETE', 'Media', (req, next) => {
|
|
||||||
const id = req.data.id
|
|
||||||
mediaDB
|
|
||||||
.chain()
|
|
||||||
.find({ $loki: id })
|
|
||||||
.remove()
|
|
||||||
return next() //> delegate to next/default handlers
|
|
||||||
})
|
|
||||||
|
|
||||||
function _formatResult (decodedMedia) {
|
|
||||||
const readable = new Readable()
|
|
||||||
const result = new Array()
|
|
||||||
readable.push(decodedMedia)
|
|
||||||
readable.push(null)
|
|
||||||
result.push({ value: readable })
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,35 +0,0 @@
|
|||||||
### Requires REST Client for VS Code
|
|
||||||
### https://marketplace.visualstudio.com/items?itemName=humao.rest-client
|
|
||||||
###
|
|
||||||
@protocol = http
|
|
||||||
@host = localhost
|
|
||||||
@port = 4004
|
|
||||||
### Read Pictures
|
|
||||||
GET {{protocol}}://{{host}}:{{port}}/media-server/Media
|
|
||||||
Authorization: Basic admin:
|
|
||||||
|
|
||||||
### Create Picture with mediatype
|
|
||||||
POST {{protocol}}://{{host}}:{{port}}/media-server/Media
|
|
||||||
Authorization: Basic admin:
|
|
||||||
Accept: application/json
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"mediaType": "image/png"
|
|
||||||
}
|
|
||||||
|
|
||||||
### Upload Binary PNG
|
|
||||||
PUT {{protocol}}://{{host}}:{{port}}/media-server/Media(1)/content
|
|
||||||
Authorization: Basic admin:
|
|
||||||
Content-Type: image/png
|
|
||||||
|
|
||||||
< ./Test.png
|
|
||||||
|
|
||||||
### Read Binary
|
|
||||||
GET {{protocol}}://{{host}}:{{port}}/media-server/Media(1)/content
|
|
||||||
Authorization: Basic admin:
|
|
||||||
|
|
||||||
### Delete Image
|
|
||||||
DELETE {{protocol}}://{{host}}:{{port}}/media-server/Media(1)
|
|
||||||
Authorization: Basic admin:
|
|
||||||
177
mta.yaml
Normal file
177
mta.yaml
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
_schema-version: 3.3.0
|
||||||
|
ID: capire.samples
|
||||||
|
version: 3.0.0
|
||||||
|
description: "A monorepo with several samples for CAP."
|
||||||
|
parameters:
|
||||||
|
enable-parallel-deployments: true
|
||||||
|
build-parameters:
|
||||||
|
before-all:
|
||||||
|
- builder: custom
|
||||||
|
commands:
|
||||||
|
- npm ci
|
||||||
|
- npx cds build ./shared-db --for hana --production
|
||||||
|
- npx cds build ./orders --for nodejs --production --ws-pack
|
||||||
|
- npx cds build ./reviews --for nodejs --production
|
||||||
|
- npx cds build ./bookstore --for nodejs --production --ws-pack
|
||||||
|
modules:
|
||||||
|
- name: bookstore-srv
|
||||||
|
type: nodejs
|
||||||
|
path: bookstore/gen/srv
|
||||||
|
parameters:
|
||||||
|
instances: 1
|
||||||
|
buildpack: nodejs_buildpack
|
||||||
|
build-parameters:
|
||||||
|
builder: npm
|
||||||
|
properties:
|
||||||
|
cds_requires_ReviewsService_credentials: {"destination": "reviews-dest","path": "/reviews"}
|
||||||
|
cds_requires_OrdersService_credentials: {"destination": "orders-dest","path": "/odata/v4/orders"}
|
||||||
|
provides:
|
||||||
|
- name: bookstore-api # required by consumers of CAP services (e.g. approuter)
|
||||||
|
properties:
|
||||||
|
srv-url: ${default-url}
|
||||||
|
requires:
|
||||||
|
- name: samples-db
|
||||||
|
- name: samples-auth
|
||||||
|
- name: samples-messaging
|
||||||
|
- name: samples-destination
|
||||||
|
|
||||||
|
- name: orders-srv
|
||||||
|
type: nodejs
|
||||||
|
path: orders/gen/srv
|
||||||
|
parameters:
|
||||||
|
instances: 1
|
||||||
|
buildpack: nodejs_buildpack
|
||||||
|
build-parameters:
|
||||||
|
builder: npm
|
||||||
|
provides:
|
||||||
|
- name: orders-api # required by consumers of CAP services (e.g. approuter)
|
||||||
|
properties:
|
||||||
|
srv-url: ${default-url}
|
||||||
|
requires:
|
||||||
|
- name: samples-db
|
||||||
|
- name: samples-auth
|
||||||
|
- name: samples-messaging
|
||||||
|
- name: samples-destination
|
||||||
|
|
||||||
|
- name: reviews-srv
|
||||||
|
type: nodejs
|
||||||
|
path: reviews/gen/srv
|
||||||
|
parameters:
|
||||||
|
instances: 1
|
||||||
|
buildpack: nodejs_buildpack
|
||||||
|
build-parameters:
|
||||||
|
builder: npm
|
||||||
|
provides:
|
||||||
|
- name: reviews-api # required by consumers of CAP services (e.g. approuter)
|
||||||
|
properties:
|
||||||
|
srv-url: ${default-url}
|
||||||
|
requires:
|
||||||
|
- name: samples-db
|
||||||
|
- name: samples-auth
|
||||||
|
- name: samples-messaging
|
||||||
|
- name: samples-destination
|
||||||
|
|
||||||
|
- name: samples-db-deployer
|
||||||
|
type: hdb
|
||||||
|
path: shared-db/gen/db
|
||||||
|
parameters:
|
||||||
|
buildpack: nodejs_buildpack
|
||||||
|
requires:
|
||||||
|
- name: samples-db
|
||||||
|
|
||||||
|
- name: samples
|
||||||
|
type: approuter.nodejs
|
||||||
|
path: app/router
|
||||||
|
parameters:
|
||||||
|
keep-existing-routes: true
|
||||||
|
disk-quota: 256M
|
||||||
|
memory: 256M
|
||||||
|
requires:
|
||||||
|
- name: orders-api
|
||||||
|
group: destinations
|
||||||
|
properties:
|
||||||
|
name: orders-api
|
||||||
|
url: ~{srv-url}
|
||||||
|
forwardAuthToken: true
|
||||||
|
- name: reviews-api
|
||||||
|
group: destinations
|
||||||
|
properties:
|
||||||
|
name: reviews-api
|
||||||
|
url: ~{srv-url}
|
||||||
|
forwardAuthToken: true
|
||||||
|
- name: bookstore-api
|
||||||
|
group: destinations
|
||||||
|
properties:
|
||||||
|
name: bookstore-api
|
||||||
|
url: ~{srv-url}
|
||||||
|
forwardAuthToken: true
|
||||||
|
- name: samples-auth
|
||||||
|
- name: samples-destination
|
||||||
|
provides:
|
||||||
|
- name: app-api
|
||||||
|
properties:
|
||||||
|
app-protocol: ${protocol}
|
||||||
|
app-uri: ${default-uri}
|
||||||
|
|
||||||
|
- name: destination-content
|
||||||
|
type: com.sap.application.content
|
||||||
|
requires:
|
||||||
|
- name: orders-api
|
||||||
|
- name: reviews-api
|
||||||
|
- name: bookstore-api
|
||||||
|
- name: samples-auth
|
||||||
|
parameters:
|
||||||
|
service-key:
|
||||||
|
name: xsuaa_service-key
|
||||||
|
- name: samples-destination
|
||||||
|
parameters:
|
||||||
|
content-target: true
|
||||||
|
build-parameters:
|
||||||
|
no-source: true
|
||||||
|
parameters:
|
||||||
|
content:
|
||||||
|
instance:
|
||||||
|
existing_destinations_policy: update
|
||||||
|
destinations:
|
||||||
|
- Name: orders-dest
|
||||||
|
URL: ~{orders-api/srv-url}
|
||||||
|
Authentication: OAuth2ClientCredentials
|
||||||
|
TokenServiceInstanceName: samples-auth
|
||||||
|
TokenServiceKeyName: xsuaa_service-key
|
||||||
|
- Name: reviews-dest
|
||||||
|
URL: ~{reviews-api/srv-url}
|
||||||
|
Authentication: OAuth2ClientCredentials
|
||||||
|
TokenServiceInstanceName: samples-auth
|
||||||
|
TokenServiceKeyName: xsuaa_service-key
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- name: samples-db
|
||||||
|
type: com.sap.xs.hdi-container
|
||||||
|
parameters:
|
||||||
|
service: hana
|
||||||
|
service-plan: hdi-shared
|
||||||
|
- name: samples-auth
|
||||||
|
type: org.cloudfoundry.managed-service
|
||||||
|
processed-after:
|
||||||
|
- samples-messaging
|
||||||
|
parameters:
|
||||||
|
service: xsuaa
|
||||||
|
service-plan: application
|
||||||
|
path: ./xs-security.json
|
||||||
|
config:
|
||||||
|
xsappname: samples-${org}-${space}
|
||||||
|
tenant-mode: dedicated
|
||||||
|
- name: samples-messaging
|
||||||
|
type: org.cloudfoundry.managed-service
|
||||||
|
parameters:
|
||||||
|
service: enterprise-messaging
|
||||||
|
service-plan: default
|
||||||
|
path: ./event-mesh.json
|
||||||
|
config:
|
||||||
|
emname: bookstore-${org}-${space}
|
||||||
|
namespace: cap/samples/${space}
|
||||||
|
- name: samples-destination
|
||||||
|
type: org.cloudfoundry.managed-service
|
||||||
|
parameters:
|
||||||
|
service: destination
|
||||||
|
service-plan: lite
|
||||||
3
orders/_i18n/i18n_de.properties
Normal file
3
orders/_i18n/i18n_de.properties
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Order = Bestellung
|
||||||
|
Orders = Bestellungen
|
||||||
|
Price = Preis
|
||||||
10
orders/_i18n/i18n_en.properties
Normal file
10
orders/_i18n/i18n_en.properties
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
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
|
||||||
10
orders/_i18n/i18n_fr.properties
Normal file
10
orders/_i18n/i18n_fr.properties
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Order = Order
|
||||||
|
Orders = Orders
|
||||||
|
OrderNo = Numéro de commande
|
||||||
|
OrderItems = Articles de la commande
|
||||||
|
Customer = Client
|
||||||
|
Product = Produit
|
||||||
|
ProductID = ID du produit
|
||||||
|
ProductTitle = Titre du produit
|
||||||
|
UnitPrice = Prix unitaire
|
||||||
|
Quantity = Quantité
|
||||||
@@ -2,7 +2,7 @@ using { Currency, User, managed, cuid } from '@sap/cds/common';
|
|||||||
namespace sap.capire.orders;
|
namespace sap.capire.orders;
|
||||||
|
|
||||||
entity Orders : cuid, managed {
|
entity Orders : cuid, managed {
|
||||||
OrderNo : String(22) @title:'Order Number'; //> readable key
|
OrderNo : String(44) @title:'Order Number'; //> readable key
|
||||||
Items : Composition of many {
|
Items : Composition of many {
|
||||||
key ID : UUID;
|
key ID : UUID;
|
||||||
product : Association to Products;
|
product : Association to Products;
|
||||||
|
|||||||
@@ -2,7 +2,18 @@
|
|||||||
"name": "@capire/orders",
|
"name": "@capire/orders",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cap-js/hana": "^1.7.0",
|
||||||
"@capire/common": "*",
|
"@capire/common": "*",
|
||||||
"@sap/cds": ">=5"
|
"@sap/cds": ">=5",
|
||||||
|
"@sap/xssec": "^4.4.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "cds-serve"
|
||||||
|
},
|
||||||
|
"cds": {
|
||||||
|
"requires": {
|
||||||
|
"messaging": true,
|
||||||
|
"db": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,4 +2,14 @@ using { sap.capire.orders as my } from '../db/schema';
|
|||||||
|
|
||||||
service OrdersService {
|
service OrdersService {
|
||||||
entity Orders as projection on my.Orders;
|
entity Orders as projection on my.Orders;
|
||||||
|
|
||||||
|
event OrderChanged {
|
||||||
|
product: String;
|
||||||
|
deltaQuantity: Integer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@odata.draft.bypass
|
||||||
|
@(requires: 'system-user')
|
||||||
|
entity OrdersNoDraft as projection on my.Orders;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class OrdersService extends cds.ApplicationService {
|
|||||||
/** order changed -> broadcast event */
|
/** order changed -> broadcast event */
|
||||||
orderChanged (product, deltaQuantity) {
|
orderChanged (product, deltaQuantity) {
|
||||||
// Emit events to inform subscribers about changes in orders
|
// Emit events to inform subscribers about changes in orders
|
||||||
console.log ('> emitting:', 'OrderChanged', { product, deltaQuantity })
|
console.log ('> emitting:', 'OrderChanged', { product, deltaQuantity }) // eslint-disable-line no-console
|
||||||
return this.emit ('OrderChanged', { product, deltaQuantity })
|
return this.emit ('OrderChanged', { product, deltaQuantity })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7208
package-lock.json
generated
7208
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
114
package.json
114
package.json
@@ -1,58 +1,62 @@
|
|||||||
{
|
{
|
||||||
"name": "@capire/samples",
|
"name": "@capire/samples",
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"description": "A monorepo with several samples for CAP.",
|
"description": "A monorepo with several samples for CAP.",
|
||||||
"repository": "https://github.com/sap-samples/cloud-cap-samples.git",
|
"repository": "https://github.com/sap-samples/cloud-cap-samples.git",
|
||||||
"author": "daniel.hutzel@sap.com",
|
"author": "daniel.hutzel@sap.com",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": ">=7"
|
"@sap/cds": ">=8",
|
||||||
|
"@cap-js/hana": "^1",
|
||||||
|
"@sap/xssec": "^4"
|
||||||
|
},
|
||||||
|
"workspaces": [
|
||||||
|
"./bookshop",
|
||||||
|
"./bookstore",
|
||||||
|
"./common",
|
||||||
|
"./fiori",
|
||||||
|
"./orders",
|
||||||
|
"./reviews",
|
||||||
|
"./etc/data-viewer",
|
||||||
|
"./etc/loggers"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"@cap-js/cds-test": "^0",
|
||||||
|
"@cap-js/cds-types": "^0",
|
||||||
|
"@cap-js/sqlite": "^1",
|
||||||
|
"@sap/cds-dk": "^8",
|
||||||
|
"eslint": "^9",
|
||||||
|
"semver": "^7"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "mbt build -t gen --mtar mta.tar",
|
||||||
|
"deploy": "cf deploy gen/mta.tar",
|
||||||
|
"undeploy": "cf undeploy capire.samples --delete-services --delete-service-keys",
|
||||||
|
"bookshop": "cds watch bookshop",
|
||||||
|
"fiori": "cds watch fiori",
|
||||||
|
"lint": "eslint",
|
||||||
|
"test": "npx jest --silent",
|
||||||
|
"jest": "npx jest --silent",
|
||||||
|
"mocha": "CDS_TEST_SILENT=y npx mocha"
|
||||||
|
},
|
||||||
|
"mocha": {
|
||||||
|
"recursive": true,
|
||||||
|
"parallel": true,
|
||||||
|
"timeout": 6666
|
||||||
|
},
|
||||||
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
|
"private": true,
|
||||||
|
"cds": {
|
||||||
|
"sql": {
|
||||||
|
"native_hana_associations": false
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"requires": {
|
||||||
"./bookshop",
|
"[production]": {
|
||||||
"./bookstore",
|
"auth": "xsuaa"
|
||||||
"./common",
|
},
|
||||||
"./data-viewer",
|
"messaging": {
|
||||||
"./fiori",
|
"kind": "enterprise-messaging"
|
||||||
"./hello",
|
},
|
||||||
"./media",
|
"destinations": true
|
||||||
"./orders",
|
}
|
||||||
"./loggers",
|
|
||||||
"./reviews"
|
|
||||||
],
|
|
||||||
"devDependencies": {
|
|
||||||
"@cap-js/sqlite": "^1",
|
|
||||||
"@sap/eslint-plugin-cds": "^3",
|
|
||||||
"axios": "^1",
|
|
||||||
"chai": "^4.3.4",
|
|
||||||
"chai-as-promised": "^7.1.1",
|
|
||||||
"chai-subset": "^1.6.0",
|
|
||||||
"eslint": "^9",
|
|
||||||
"semver": "^7"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules",
|
|
||||||
"bookshop": "cds watch bookshop",
|
|
||||||
"fiori": "cds watch fiori",
|
|
||||||
"hello": "cds watch hello",
|
|
||||||
"media": "cds watch media",
|
|
||||||
"mocha": "CDS_TEST_SILENT=y npx mocha",
|
|
||||||
"jest": "npx jest --silent",
|
|
||||||
"start": "cds watch fiori",
|
|
||||||
"test": "npm run jest -- --silent",
|
|
||||||
"test:hello": "cd hello && npm test",
|
|
||||||
"lint": "eslint ."
|
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"testTimeout": 20000,
|
|
||||||
"testMatch": [
|
|
||||||
"**/*.test.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"mocha": {
|
|
||||||
"recursive": true,
|
|
||||||
"parallel": true,
|
|
||||||
"timeout": 6666
|
|
||||||
},
|
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
|
||||||
"private": true
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
3
reviews/_i18n/i18n_de.properties
Normal file
3
reviews/_i18n/i18n_de.properties
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Date = Datum
|
||||||
|
Rating = Bewertung
|
||||||
|
NumberOfReviews = Anzahl der Reviews
|
||||||
5
reviews/_i18n/i18n_en.properties
Normal file
5
reviews/_i18n/i18n_en.properties
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Reviews = Reviews
|
||||||
|
Review = Review
|
||||||
|
Date = Date
|
||||||
|
Rating = Rating
|
||||||
|
NumberOfReviews = Number of Reviews
|
||||||
5
reviews/_i18n/i18n_fr.properties
Normal file
5
reviews/_i18n/i18n_fr.properties
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Reviews = Avis
|
||||||
|
Review = Avis
|
||||||
|
Date = Date
|
||||||
|
Rating = Note
|
||||||
|
NumberOfReviews = Nombre d''avis
|
||||||
@@ -50,7 +50,6 @@ const reviews = Vue.createApp ({
|
|||||||
const res = await POST(`/Reviews`,review)
|
const res = await POST(`/Reviews`,review)
|
||||||
reviews.ID = res.data.ID
|
reviews.ID = res.data.ID
|
||||||
} else {
|
} else {
|
||||||
console.trace()
|
|
||||||
await PUT(`/Reviews/${review.ID}`,review)
|
await PUT(`/Reviews/${review.ID}`,review)
|
||||||
}
|
}
|
||||||
reviews.message = { succeeded: 'Your review was submitted successfully. Thanks.' }
|
reviews.message = { succeeded: 'Your review was submitted successfully. Thanks.' }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
subject;rating;reviewer;title;text
|
ID;subject;rating;reviewer;title;text
|
||||||
201;5;bob;Intriguing;Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
1689144d-3b10-4849-bcbe-2408a13e161a;201;5;bob;Intriguing;Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||||
201;4;bob;Fascinating;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id diam maecenas ultricies mi eget mauris pharetra et. Risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci eu. Nec sagittis aliquam malesuada bibendum arcu. Eu consequat ac felis donec. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Tortor posuere ac ut consequat semper viverra nam libero. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Scelerisque purus semper eget duis at tellus. Elementum tempus egestas sed sed risus pretium. Arcu dictum varius duis at. Amet luctus venenatis lectus magna fringilla urna. Eget velit aliquet sagittis id consectetur purus ut faucibus. Vitae auctor eu augue ut lectus. Fermentum iaculis eu non diam phasellus vestibulum.
|
1689144d-3b10-4849-bcbe-2408a13e161b;201;4;bob;Fascinating;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id diam maecenas ultricies mi eget mauris pharetra et. Risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci eu. Nec sagittis aliquam malesuada bibendum arcu. Eu consequat ac felis donec. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Tortor posuere ac ut consequat semper viverra nam libero. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Scelerisque purus semper eget duis at tellus. Elementum tempus egestas sed sed risus pretium. Arcu dictum varius duis at. Amet luctus venenatis lectus magna fringilla urna. Eget velit aliquet sagittis id consectetur purus ut faucibus. Vitae auctor eu augue ut lectus. Fermentum iaculis eu non diam phasellus vestibulum.
|
||||||
207;2;bob;What is this?;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Libero justo laoreet sit amet cursus sit amet dictum. Nunc faucibus a pellentesque sit. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Enim nunc faucibus a pellentesque. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Cras ornare arcu dui vivamus. Facilisi etiam dignissim diam quis enim lobortis. Et molestie ac feugiat sed. Urna neque viverra justo nec ultrices dui. Ullamcorper a lacus vestibulum sed arcu non. Volutpat ac tincidunt vitae semper quis. Dignissim sodales ut eu sem. Feugiat in fermentum posuere urna nec. At augue eget arcu dictum varius.
|
1689144d-3b10-4849-bcbe-2408a13e161c;207;2;bob;What is this?;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Libero justo laoreet sit amet cursus sit amet dictum. Nunc faucibus a pellentesque sit. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Enim nunc faucibus a pellentesque. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Cras ornare arcu dui vivamus. Facilisi etiam dignissim diam quis enim lobortis. Et molestie ac feugiat sed. Urna neque viverra justo nec ultrices dui. Ullamcorper a lacus vestibulum sed arcu non. Volutpat ac tincidunt vitae semper quis. Dignissim sodales ut eu sem. Feugiat in fermentum posuere urna nec. At augue eget arcu dictum varius.
|
||||||
251;3;bob;It's dark...;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Suscipit tellus mauris a diam. Velit aliquet sagittis id consectetur purus ut. Viverra adipiscing at in tellus integer. Vitae elementum curabitur vitae nunc. Mattis ullamcorper velit sed ullamcorper morbi. Diam quis enim lobortis scelerisque. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Semper auctor neque vitae tempus. Quis eleifend quam adipiscing vitae proin. Neque convallis a cras semper auctor neque vitae. Imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet consectetur adipiscing elit ut aliquam purus. Pretium quam vulputate dignissim suspendisse.
|
1689144d-3b10-4849-bcbe-2408a13e161d;251;3;bob;It's dark...;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Suscipit tellus mauris a diam. Velit aliquet sagittis id consectetur purus ut. Viverra adipiscing at in tellus integer. Vitae elementum curabitur vitae nunc. Mattis ullamcorper velit sed ullamcorper morbi. Diam quis enim lobortis scelerisque. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Semper auctor neque vitae tempus. Quis eleifend quam adipiscing vitae proin. Neque convallis a cras semper auctor neque vitae. Imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet consectetur adipiscing elit ut aliquam purus. Pretium quam vulputate dignissim suspendisse.
|
||||||
|
@@ -7,17 +7,28 @@
|
|||||||
"index.cds"
|
"index.cds"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cap-js/hana": "^1.7.0",
|
||||||
"@sap/cds": ">=5",
|
"@sap/cds": ">=5",
|
||||||
|
"@sap/xssec": "^4.4.0",
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "cds-serve"
|
||||||
|
},
|
||||||
"cds": {
|
"cds": {
|
||||||
"requires": {
|
"requires": {
|
||||||
"messaging": {
|
"messaging": {
|
||||||
"[development]": { "kind": "file-based-messaging" },
|
"[development]": {
|
||||||
"[hybrid]": { "kind": "enterprise-messaging-shared" },
|
"kind": "file-based-messaging"
|
||||||
"[production]": { "kind": "enterprise-messaging" }
|
},
|
||||||
|
"[hybrid]": {
|
||||||
|
"kind": "enterprise-messaging-shared"
|
||||||
|
},
|
||||||
|
"[production]": {
|
||||||
|
"kind": "enterprise-messaging"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"db": { "kind": "sql" }
|
"db": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ module.exports = cds.service.impl (function(){
|
|||||||
const { count, rating } = await cds.tx(req) .run (
|
const { count, rating } = await cds.tx(req) .run (
|
||||||
SELECT.one `round(avg(rating),2) as rating, count(*) as count` .from (Reviews) .where ({subject})
|
SELECT.one `round(avg(rating),2) as rating, count(*) as count` .from (Reviews) .where ({subject})
|
||||||
)
|
)
|
||||||
global.it || console.log ('< emitting:', 'reviewed', { subject, count, rating })
|
global.it || console.log ('< emitting:', 'reviewed', { subject, count, rating }) // eslint-disable-line no-console
|
||||||
await this.emit ('reviewed', { subject, count, rating })
|
await this.emit ('reviewed', { subject, count, rating })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
10
samples.md
10
samples.md
@@ -6,12 +6,6 @@ Each sub directory essentially is an individual npm package arranged in an [all-
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
## [@capire/hello-world](hello)
|
|
||||||
|
|
||||||
- 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).
|
|
||||||
- [Typescript support](https://cap.cloud.sap/docs/node.js/typescript)
|
|
||||||
|
|
||||||
|
|
||||||
## [@capire/bookshop](bookshop)
|
## [@capire/bookshop](bookshop)
|
||||||
|
|
||||||
- [Getting Started](https://cap.cloud.sap/docs/get-started/in-a-nutshell) with CAP, briefly introducing:
|
- [Getting Started](https://cap.cloud.sap/docs/get-started/in-a-nutshell) with CAP, briefly introducing:
|
||||||
@@ -60,10 +54,10 @@ Each sub directory essentially is an individual npm package arranged in an [all-
|
|||||||
- [@capire/reviews](reviews)
|
- [@capire/reviews](reviews)
|
||||||
- [@capire/orders](orders)
|
- [@capire/orders](orders)
|
||||||
- [@capire/common](common)
|
- [@capire/common](common)
|
||||||
- [@capire/data-viewer](data-viewer)
|
- [@capire/data-viewer](etc/data-viewer)
|
||||||
- [The Vue.js app](bookshop/app/vue) imported from `bookshop` is served as well
|
- [The Vue.js app](bookshop/app/vue) imported from `bookshop` is served as well
|
||||||
- [The Vue.js app](reviews/app/vue) imported from `reviews` is served as well
|
- [The Vue.js app](reviews/app/vue) imported from `reviews` is served as well
|
||||||
- [The Vue.js app](data-viewer/app/data) imported from `data-viewer` is served as well
|
- [The Vue.js app](etc/data-viewer/app/data) imported from `data-viewer` is served as well
|
||||||
- [The Fiori app](orders/app) imported from `orders` is served as well
|
- [The Fiori app](orders/app) imported from `orders` is served as well
|
||||||
- [OpenAPI export + Swagger UI](https://cap.cloud.sap/docs/advanced/openapi)
|
- [OpenAPI export + Swagger UI](https://cap.cloud.sap/docs/advanced/openapi)
|
||||||
|
|
||||||
|
|||||||
3
shared-db/db/index.cds
Normal file
3
shared-db/db/index.cds
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
using from '@capire/bookstore';
|
||||||
|
using from '@capire/reviews';
|
||||||
|
using from '@capire/orders';
|
||||||
17
shared-db/package.json
Normal file
17
shared-db/package.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "shared-db",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"cds": {
|
||||||
|
"sql": {
|
||||||
|
"native_hana_associations": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
const cds = require('@sap/cds')
|
||||||
|
const { expect } = cds.test
|
||||||
|
|
||||||
describe('cds.ql → cqn', () => {
|
describe('cds.ql → cqn', () => {
|
||||||
|
|
||||||
const cds = require('@sap/cds/lib')
|
|
||||||
const { expect } = cds.test
|
|
||||||
const Foo = { name: 'Foo' }
|
const Foo = { name: 'Foo' }
|
||||||
const Books = { name: 'capire.bookshop.Books' }
|
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(Foo).entries(entries))
|
.to.eql(INSERT.into(Foo).entries(entries))
|
||||||
.to.eql({
|
.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(Foo).columns('a', 'b').rows([1, 2], [3, 4]))
|
||||||
.to.eql({
|
.to.eql({
|
||||||
INSERT: {
|
INSERT: {
|
||||||
into: cds.env.ql.quirks_mode ? 'Foo' : { ref: ['Foo'] },
|
into: { ref: ['Foo'] },
|
||||||
columns: ['a', 'b'],
|
columns: ['a', 'b'],
|
||||||
rows: [
|
rows: [
|
||||||
[1, 2],
|
[1, 2],
|
||||||
@@ -706,7 +707,7 @@ describe('cds.ql → cqn', () => {
|
|||||||
expect(INSERT.into(Foo).columns('a', 'b').values([1, 2]))
|
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(Foo).columns('a', 'b').values(1, 2))
|
||||||
.to.eql({
|
.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>)', () => {
|
test('entity (..., <key>)', () => {
|
||||||
const cqnWhere = {
|
const cqnWhere = {
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
entity: cds.env.ql.quirks_mode ? 'capire.bookshop.Books' : { ref: ['capire.bookshop.Books'] },
|
entity: { ref: ['capire.bookshop.Books'] },
|
||||||
where: [{ ref: ['ID'] }, '=', { val: 4711 }],
|
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(Foo).with({ foo: 11, bar: { '-=': 22 } }))
|
||||||
.to.eql({
|
.to.eql({
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
entity: cds.env.ql.quirks_mode ? 'Foo' : { ref: ['Foo'] },
|
entity: { ref: ['Foo'] },
|
||||||
data: { foo: 11 },
|
data: { foo: 11 },
|
||||||
with: {
|
with: {
|
||||||
bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] },
|
bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] },
|
||||||
@@ -776,7 +777,7 @@ describe('cds.ql → cqn', () => {
|
|||||||
// some more
|
// some more
|
||||||
expect(UPDATE(Foo).with(`bar = coalesce(x,y), car = 'foo''s bar, car'`)).to.eql({
|
expect(UPDATE(Foo).with(`bar = coalesce(x,y), car = 'foo''s bar, car'`)).to.eql({
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
entity: cds.env.ql.quirks_mode ? 'Foo' : { ref: ['Foo'] },
|
entity: { ref: ['Foo'] },
|
||||||
data: {
|
data: {
|
||||||
car: "foo's bar, car",
|
car: "foo's bar, car",
|
||||||
},
|
},
|
||||||
@@ -796,7 +797,7 @@ describe('cds.ql → cqn', () => {
|
|||||||
test('from (..., <key>)', () => {
|
test('from (..., <key>)', () => {
|
||||||
const cqnWhere = {
|
const cqnWhere = {
|
||||||
DELETE: {
|
DELETE: {
|
||||||
from: cds.env.ql.quirks_mode ? 'capire.bookshop.Books' : { ref: ['capire.bookshop.Books'] },
|
from: { ref: ['capire.bookshop.Books'] },
|
||||||
where: [{ ref: ['ID'] }, '=', { val: 4711 }],
|
where: [{ ref: ['ID'] }, '=', { val: 4711 }],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
53
test/consuming-actions.test.js
Normal file
53
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,4 +1,4 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds')
|
||||||
|
|
||||||
describe('cap/samples - Consuming Services locally', () => {
|
describe('cap/samples - Consuming Services locally', () => {
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds')
|
||||||
|
|
||||||
describe('cap/samples - Custom Handlers', () => {
|
describe('cap/samples - Custom Handlers', () => {
|
||||||
|
|
||||||
@@ -8,9 +8,10 @@ describe('cap/samples - Custom Handlers', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should reject out-of-stock orders', async () => {
|
it('should reject out-of-stock orders', async () => {
|
||||||
await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
|
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled
|
||||||
await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
|
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/)
|
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`
|
const { data } = await GET`/admin/Books/201/stock/$value`
|
||||||
expect(data).to.equal(2)
|
expect(data).to.equal(2)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
// Quick hack: suppress deprecation warnings w/ Node22 caused by http-proxy (used by OData v2 proxy)
|
||||||
|
// See also: https://github.com/http-party/node-http-proxy/pull/1666
|
||||||
|
require('util')._extend = Object.assign
|
||||||
|
|
||||||
|
const cds = require('@sap/cds')
|
||||||
|
|
||||||
describe('cap/samples - Fiori APIs - v2', function() {
|
describe('cap/samples - Fiori APIs - v2', function() {
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
|
||||||
|
|
||||||
describe('cap/samples - Hello world!', () => {
|
|
||||||
|
|
||||||
const { GET, expect } = cds.test (__dirname+'/../hello')
|
|
||||||
|
|
||||||
it('should say hello with class impl', async () => {
|
|
||||||
const {data} = await GET `/say/hello(to='world')`
|
|
||||||
expect(data.value).to.eql('Hello world!')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should say hello with another impl', async () => {
|
|
||||||
await cds.serve('say').from(cds.model)
|
|
||||||
.at('/say-again').in(cds.app)
|
|
||||||
.with(srv => {
|
|
||||||
srv.on('hello', (req) => `Hello again ${req.data.to}!`)
|
|
||||||
})
|
|
||||||
const {data} = await GET `/say-again/hello(to='world')`
|
|
||||||
expect(data.value).to.eql('Hello again world!')
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds')
|
||||||
const { expect } = cds.test.in(__dirname,'..')
|
const { expect } = cds.test.in(__dirname,'..')
|
||||||
|
|
||||||
describe('cap/samples - Hierarchical Data', ()=>{
|
describe('cap/samples - Hierarchical Data', ()=>{
|
||||||
@@ -77,49 +77,45 @@ describe('cap/samples - Hierarchical Data', ()=>{
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('supports nested reads', async()=>{
|
it ('supports nested reads', ()=> expect (
|
||||||
expect (await
|
SELECT.one.from (Cats, c=>{
|
||||||
SELECT.one.from (Cats, c=>{
|
c.ID, c.name.as('parent'), c.children (c=>{
|
||||||
c.ID, c.name.as('parent'), c.children (c=>{
|
c.name.as('child')
|
||||||
c.name.as('child')
|
})
|
||||||
})
|
}) .where ({name:'Cat'})
|
||||||
}) .where ({name:'Cat'})
|
) .to.eventually.eql (
|
||||||
) .to.eql (
|
{ ID:101, parent:'Cat', children:[
|
||||||
{ ID:101, parent:'Cat', children:[
|
{ child:'Kitty' },
|
||||||
{ child:'Kitty' },
|
{ child:'Catwoman' },
|
||||||
{ child:'Catwoman' },
|
]}
|
||||||
]}
|
))
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it ('supports deeply nested reads', async()=>{
|
it ('supports deeply nested reads', ()=> expect (
|
||||||
expect (await SELECT.one.from (Cats, c=>{
|
SELECT.one.from (Cats, c=>{
|
||||||
c.ID, c.name, c.children (
|
c.ID, c.name, c.children (
|
||||||
c => { c.name },
|
c => { c.name },
|
||||||
{levels:3}
|
{levels:3}
|
||||||
)
|
)
|
||||||
}) .where ({name:'Cat'})
|
}) .where ({name:'Cat'})
|
||||||
) .to.eql (
|
) .to.eventually.eql (
|
||||||
{ ID:101, name:'Cat', children:[
|
{ ID:101, name:'Cat', children:[
|
||||||
{ name:'Kitty', children:[
|
{ name:'Kitty', children:[
|
||||||
{ name:'Kitty Cat', children:[
|
{ name:'Kitty Cat', children:[
|
||||||
{ name:'Aristocat' }, ]}, // level 3
|
{ name:'Aristocat' }, ]}, // level 3
|
||||||
{ name:'Kitty Bat', children:[] }, ]},
|
{ name:'Kitty Bat', children:[] }, ]},
|
||||||
{ name:'Catwoman', children:[
|
{ name:'Catwoman', children:[
|
||||||
{ name:'Catalina', children:[] } ]},
|
{ name:'Catalina', children:[] } ]},
|
||||||
]}
|
]}
|
||||||
)
|
))
|
||||||
})
|
|
||||||
|
|
||||||
it ('supports cascaded deletes', async()=>{
|
it ('supports cascaded deletes', async()=>{
|
||||||
const affectedRows = await DELETE.from (Cats) .where ({ID:[102,106]})
|
const affectedRows = await DELETE.from (Cats) .where ({ID:[102,106]})
|
||||||
expect (affectedRows) .to.be.greaterThan (0)
|
expect (affectedRows) .to.be.greaterThan (0)
|
||||||
const expected = [
|
await expect (SELECT`ID,name`.from(Cats) ).to.eventually.eql ([
|
||||||
{ ID:100, name:'Some Cats...' },
|
{ ID:100, name:'Some Cats...' },
|
||||||
{ ID:101, name:'Cat' },
|
{ ID:101, name:'Cat' },
|
||||||
{ ID:108, name:'Catweazle' }
|
{ ID:108, name:'Catweazle' }
|
||||||
]
|
])
|
||||||
expect ( await SELECT`ID,name`.from(Cats) ).to.eql (expected)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds')
|
||||||
|
|
||||||
describe('cap/samples - Localized Data', () => {
|
describe('cap/samples - Localized Data', () => {
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds')
|
||||||
|
|
||||||
describe('cap/samples - Messaging', ()=>{
|
describe('cap/samples - Messaging', ()=>{
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds')
|
||||||
|
|
||||||
describe('cap/samples - Bookshop APIs', () => {
|
describe('cap/samples - Bookshop APIs', () => {
|
||||||
const { GET, expect, axios } = cds.test ('@capire/bookshop')
|
const { GET, expect, axios } = cds.test ('@capire/bookshop')
|
||||||
@@ -8,9 +8,10 @@ describe('cap/samples - Bookshop APIs', () => {
|
|||||||
const { headers, status, data } = await GET `/browse/$metadata`
|
const { headers, status, data } = await GET `/browse/$metadata`
|
||||||
expect(status).to.equal(200)
|
expect(status).to.equal(200)
|
||||||
expect(headers).to.contain({
|
expect(headers).to.contain({
|
||||||
'content-type': 'application/xml',
|
// 'content-type': 'application/xml', //> fails with 'application/xml;charset=utf-8', which is set by express
|
||||||
'odata-version': '4.0',
|
'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('<EntitySet Name="Books" EntityType="CatalogService.Books">')
|
||||||
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>')
|
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>')
|
||||||
})
|
})
|
||||||
@@ -28,63 +29,66 @@ describe('cap/samples - Bookshop APIs', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('supports $search in multiple fields', async () => {
|
describe('query options...', () => {
|
||||||
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 () => {
|
it('supports $search in multiple fields', async () => {
|
||||||
const { data } = await GET(`/browse/Books`, {
|
const { data } = await GET `/browse/Books ${{
|
||||||
params: { $select: `ID,title` },
|
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' },
|
||||||
|
])
|
||||||
})
|
})
|
||||||
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 () => {
|
it('supports $select', async () => {
|
||||||
const { data } = await GET(`/admin/Authors`, {
|
const { data } = await GET(`/browse/Books`, {
|
||||||
params: {
|
params: { $select: `ID,title` },
|
||||||
$select: `name`,
|
})
|
||||||
$expand: `books($select=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' },
|
||||||
|
])
|
||||||
})
|
})
|
||||||
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 () => {
|
it('supports $expand', async () => {
|
||||||
const { data } = await GET`/admin/Books/201/stock/$value`
|
const { data } = await GET(`/admin/Authors`, {
|
||||||
expect(data).to.equal(12)
|
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 $top/$skip paging', async () => {
|
it('supports $value requests', async () => {
|
||||||
const { data: p1 } = await GET`/browse/Books?$select=title&$top=3`
|
const { data } = await GET`/admin/Books/201/stock/$value`
|
||||||
expect(p1.value).to.containSubset([
|
expect(data).to.equal(12)
|
||||||
{ ID: 201, title: 'Wuthering Heights' },
|
})
|
||||||
{ ID: 207, title: 'Jane Eyre' },
|
|
||||||
{ ID: 251, title: 'The Raven' },
|
it('supports $top/$skip paging', async () => {
|
||||||
])
|
const { data: p1 } = await GET`/browse/Books?$select=title&$top=3`
|
||||||
const { data: p2 } = await GET`/browse/Books?$select=title&$skip=3`
|
expect(p1.value).to.containSubset([
|
||||||
expect(p2.value).to.containSubset([
|
{ ID: 201, title: 'Wuthering Heights' },
|
||||||
{ ID: 252, title: 'Eleonora' },
|
{ ID: 207, title: 'Jane Eyre' },
|
||||||
{ ID: 271, title: 'Catweazle' },
|
{ 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 () => {
|
it('serves user info', async () => {
|
||||||
|
|||||||
34
xs-security.json
Normal file
34
xs-security.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.admin",
|
||||||
|
"description": "admin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.emcallback",
|
||||||
|
"description": "Enterprise-Messaging Callback Access",
|
||||||
|
"grant-as-authority-to-apps": [
|
||||||
|
"$XSSERVICENAME(samples-messaging)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.emmanagement",
|
||||||
|
"description": "Enterprise-Messaging Management Access"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attributes": [],
|
||||||
|
"role-templates": [
|
||||||
|
{
|
||||||
|
"name": "admin",
|
||||||
|
"scope-references": [
|
||||||
|
"$XSAPPNAME.admin"
|
||||||
|
],
|
||||||
|
"description": "cap samples multi-service shared-db"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"authorities-inheritance": false,
|
||||||
|
"authorities": [
|
||||||
|
"$XSAPPNAME.emmanagement",
|
||||||
|
"$XSAPPNAME.mtcallback"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user