Compare commits
2 Commits
sandbox_vm
...
cg-deploy-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f8a78fe8a | ||
|
|
7f9474244b |
@@ -16,8 +16,7 @@ app.use('/-/:tarball', (req,res,next) => {
|
|||||||
console.debug ('GET', req.params)
|
console.debug ('GET', req.params)
|
||||||
try {
|
try {
|
||||||
const { tarball } = req.params
|
const { tarball } = req.params
|
||||||
const pkgFull = tarball.substring(0, tarball.lastIndexOf('-'))
|
const [, pkg ] = /^\w+-(\w+)/.exec(tarball)
|
||||||
const [, pkg ] = /^\w+-(.+)/.exec(pkgFull)
|
|
||||||
fs.lstat(tarball,(err => {
|
fs.lstat(tarball,(err => {
|
||||||
if (err) console.debug (`npm pack ../${pkg}`)
|
if (err) console.debug (`npm pack ../${pkg}`)
|
||||||
if (err) exec(`npm pack ../${pkg}`,{cwd},next)
|
if (err) exec(`npm pack ../${pkg}`,{cwd},next)
|
||||||
@@ -32,7 +31,7 @@ app.use('/-/:tarball', (req,res,next) => {
|
|||||||
app.use('/-', express.static(__dirname))
|
app.use('/-', express.static(__dirname))
|
||||||
|
|
||||||
app.get('/*', (req,res)=>{
|
app.get('/*', (req,res)=>{
|
||||||
const urlRegex = /^\/(@[\w-]+)\/(.+)/
|
const urlRegex = /^\/(@\w+)\/(\w+)/
|
||||||
const url = decodeURIComponent(req.url)
|
const url = decodeURIComponent(req.url)
|
||||||
console.debug ('GET',url)
|
console.debug ('GET',url)
|
||||||
try {
|
try {
|
||||||
|
|||||||
3752
bookshop/app/package-lock.json
generated
Normal file
3752
bookshop/app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
bookshop/app/package.json
Normal file
12
bookshop/app/package.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "approuter",
|
||||||
|
"dependencies": {
|
||||||
|
"@sap/approuter": "^10"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^16"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node node_modules/@sap/approuter/approuter.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,22 +40,19 @@ const books = Vue.createApp ({
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
books.order = { quantity, failed: e.response.data.error ? e.response.data.error.message : e.response.data }
|
books.order = { quantity, failed: e.response.data.error ? e.response.data.error.message : e.response.data }
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
async fetchUserInfo() {
|
|
||||||
try {
|
|
||||||
const { data } = await axios.get('/user/me')
|
|
||||||
books.user = data
|
|
||||||
} catch (err) { books.user = { id: err.message } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}).mount("#app")
|
}).mount("#app")
|
||||||
|
|
||||||
// initially fill list of books
|
// initially fill list of books
|
||||||
books.fetch()
|
books.fetch()
|
||||||
|
|
||||||
books.fetchUserInfo()
|
// show user info on request
|
||||||
document.addEventListener('keydown', (event) => {
|
document.addEventListener('keydown', async (event) => {
|
||||||
// hide user info on request
|
if (event.key === 'u') {
|
||||||
if (event.key === 'u') books.user = undefined
|
try {
|
||||||
|
books.user = (await axios.get('/user/User')).data
|
||||||
|
} catch (err) { }
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,10 +18,10 @@
|
|||||||
<body class="small-container", style="margin-top: 70px;">
|
<body class="small-container", style="margin-top: 70px;">
|
||||||
<div id='app'>
|
<div id='app'>
|
||||||
|
|
||||||
<div v-if="user" class="user">
|
<div v-if="user.ID && user.ID !== 'anonymous'" class="user">
|
||||||
<div>User: {{ user.id || 'anonymous' }}</div>
|
<div>User: {{ user.ID }}</div>
|
||||||
<div>Locale: {{ user.locale }}</div>
|
<div>Locale: {{ user.locale }}</div>
|
||||||
<div v-if="user.tenant">Tenant: {{ user.tenant }}</div>
|
<div>Tenant: {{ user.tenant }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1> Capire Books </h1>
|
<h1> Capire Books </h1>
|
||||||
|
|||||||
19
bookshop/app/xs-app.json
Normal file
19
bookshop/app/xs-app.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"authenticationMethod": "route",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"source": "^/app/(.*)$",
|
||||||
|
"target": "$1",
|
||||||
|
"localDir": ".",
|
||||||
|
"authenticationType": "xsuaa",
|
||||||
|
"cacheControl": "no-cache, no-store, must-revalidate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "^/(.*)$",
|
||||||
|
"target": "$1",
|
||||||
|
"destination": "srv-api",
|
||||||
|
"authenticationType": "xsuaa",
|
||||||
|
"csrfProtection": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
using from '..\..\schema';
|
|
||||||
|
|
||||||
@@ -1,14 +1,7 @@
|
|||||||
using {
|
using { Currency, managed, sap } from '@sap/cds/common';
|
||||||
Currency,
|
|
||||||
managed,
|
|
||||||
sap,
|
|
||||||
extensible
|
|
||||||
} from '@sap/cds/common';
|
|
||||||
|
|
||||||
namespace sap.capire.bookshop;
|
namespace sap.capire.bookshop;
|
||||||
|
|
||||||
@Extensibility.Any.Enabled : true
|
entity Books : managed {
|
||||||
entity Books : managed, extensible {
|
|
||||||
key ID : Integer;
|
key ID : Integer;
|
||||||
title : localized String(111);
|
title : localized String(111);
|
||||||
descr : localized String(1111);
|
descr : localized String(1111);
|
||||||
@@ -18,39 +11,21 @@ entity Books : managed, extensible {
|
|||||||
price : Decimal;
|
price : Decimal;
|
||||||
currency : Currency;
|
currency : Currency;
|
||||||
image : LargeBinary @Core.MediaType : 'image/png';
|
image : LargeBinary @Core.MediaType : 'image/png';
|
||||||
authorName : String;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entity Authors : managed {
|
||||||
entity Authors : managed, extensible {
|
|
||||||
key ID : Integer;
|
key ID : Integer;
|
||||||
name : String(111);
|
name : String(111);
|
||||||
dateOfBirth : Date;
|
dateOfBirth : Date;
|
||||||
dateOfDeath : Date;
|
dateOfDeath : Date;
|
||||||
placeOfBirth : String;
|
placeOfBirth : String;
|
||||||
placeOfDeath : String;
|
placeOfDeath : String;
|
||||||
|
books : Association to many Books on books.author = $self;
|
||||||
books : Association to many Books
|
|
||||||
on books.author = $self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extend Authors with {
|
/** Hierarchically organized Code List for Genres */
|
||||||
virtual age : Integer;
|
|
||||||
virtual exampleBook: String;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hierarchically organized Code List for Genres
|
|
||||||
*/
|
|
||||||
entity Genres : sap.common.CodeList {
|
entity Genres : sap.common.CodeList {
|
||||||
key ID : Integer;
|
key ID : Integer;
|
||||||
parent : Association to Genres;
|
parent : Association to Genres;
|
||||||
children : Composition of many Genres
|
children : Composition of many Genres on children.parent = $self;
|
||||||
on children.parent = $self;
|
|
||||||
}
|
|
||||||
|
|
||||||
entity Publishers: managed {
|
|
||||||
key ID: Integer;
|
|
||||||
name: String(111);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
136
bookshop/db/src/.hdiconfig
Normal file
136
bookshop/db/src/.hdiconfig
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
{
|
||||||
|
"file_suffixes": {
|
||||||
|
"csv": {
|
||||||
|
"plugin_name": "com.sap.hana.di.tabledata.source"
|
||||||
|
},
|
||||||
|
"hdbafllangprocedure": {
|
||||||
|
"plugin_name": "com.sap.hana.di.afllangprocedure"
|
||||||
|
},
|
||||||
|
"hdbanalyticprivilege": {
|
||||||
|
"plugin_name": "com.sap.hana.di.analyticprivilege"
|
||||||
|
},
|
||||||
|
"hdbcalculationview": {
|
||||||
|
"plugin_name": "com.sap.hana.di.calculationview"
|
||||||
|
},
|
||||||
|
"hdbcollection": {
|
||||||
|
"plugin_name": "com.sap.hana.di.collection"
|
||||||
|
},
|
||||||
|
"hdbconstraint": {
|
||||||
|
"plugin_name": "com.sap.hana.di.constraint"
|
||||||
|
},
|
||||||
|
"hdbdropcreatetable": {
|
||||||
|
"plugin_name": "com.sap.hana.di.dropcreatetable"
|
||||||
|
},
|
||||||
|
"hdbflowgraph": {
|
||||||
|
"plugin_name": "com.sap.hana.di.flowgraph"
|
||||||
|
},
|
||||||
|
"hdbfunction": {
|
||||||
|
"plugin_name": "com.sap.hana.di.function"
|
||||||
|
},
|
||||||
|
"hdbgraphworkspace": {
|
||||||
|
"plugin_name": "com.sap.hana.di.graphworkspace"
|
||||||
|
},
|
||||||
|
"hdbhadoopmrjob": {
|
||||||
|
"plugin_name": "com.sap.hana.di.virtualfunctionpackage.hadoop"
|
||||||
|
},
|
||||||
|
"hdbindex": {
|
||||||
|
"plugin_name": "com.sap.hana.di.index"
|
||||||
|
},
|
||||||
|
"hdblibrary": {
|
||||||
|
"plugin_name": "com.sap.hana.di.library"
|
||||||
|
},
|
||||||
|
"hdbmigrationtable": {
|
||||||
|
"plugin_name": "com.sap.hana.di.table.migration"
|
||||||
|
},
|
||||||
|
"hdbprocedure": {
|
||||||
|
"plugin_name": "com.sap.hana.di.procedure"
|
||||||
|
},
|
||||||
|
"hdbprojectionview": {
|
||||||
|
"plugin_name": "com.sap.hana.di.projectionview"
|
||||||
|
},
|
||||||
|
"hdbprojectionviewconfig": {
|
||||||
|
"plugin_name": "com.sap.hana.di.projectionview.config"
|
||||||
|
},
|
||||||
|
"hdbreptask": {
|
||||||
|
"plugin_name": "com.sap.hana.di.reptask"
|
||||||
|
},
|
||||||
|
"hdbresultcache": {
|
||||||
|
"plugin_name": "com.sap.hana.di.resultcache"
|
||||||
|
},
|
||||||
|
"hdbrole": {
|
||||||
|
"plugin_name": "com.sap.hana.di.role"
|
||||||
|
},
|
||||||
|
"hdbroleconfig": {
|
||||||
|
"plugin_name": "com.sap.hana.di.role.config"
|
||||||
|
},
|
||||||
|
"hdbsearchruleset": {
|
||||||
|
"plugin_name": "com.sap.hana.di.searchruleset"
|
||||||
|
},
|
||||||
|
"hdbsequence": {
|
||||||
|
"plugin_name": "com.sap.hana.di.sequence"
|
||||||
|
},
|
||||||
|
"hdbstatistics": {
|
||||||
|
"plugin_name": "com.sap.hana.di.statistics"
|
||||||
|
},
|
||||||
|
"hdbstructuredprivilege": {
|
||||||
|
"plugin_name": "com.sap.hana.di.structuredprivilege"
|
||||||
|
},
|
||||||
|
"hdbsynonym": {
|
||||||
|
"plugin_name": "com.sap.hana.di.synonym"
|
||||||
|
},
|
||||||
|
"hdbsynonymconfig": {
|
||||||
|
"plugin_name": "com.sap.hana.di.synonym.config"
|
||||||
|
},
|
||||||
|
"hdbsystemversioning": {
|
||||||
|
"plugin_name": "com.sap.hana.di.systemversioning"
|
||||||
|
},
|
||||||
|
"hdbtable": {
|
||||||
|
"plugin_name": "com.sap.hana.di.table"
|
||||||
|
},
|
||||||
|
"hdbtabledata": {
|
||||||
|
"plugin_name": "com.sap.hana.di.tabledata"
|
||||||
|
},
|
||||||
|
"hdbtabletype": {
|
||||||
|
"plugin_name": "com.sap.hana.di.tabletype"
|
||||||
|
},
|
||||||
|
"hdbtrigger": {
|
||||||
|
"plugin_name": "com.sap.hana.di.trigger"
|
||||||
|
},
|
||||||
|
"hdbview": {
|
||||||
|
"plugin_name": "com.sap.hana.di.view"
|
||||||
|
},
|
||||||
|
"hdbvirtualfunction": {
|
||||||
|
"plugin_name": "com.sap.hana.di.virtualfunction"
|
||||||
|
},
|
||||||
|
"hdbvirtualfunctionconfig": {
|
||||||
|
"plugin_name": "com.sap.hana.di.virtualfunction.config"
|
||||||
|
},
|
||||||
|
"hdbvirtualpackagehadoop": {
|
||||||
|
"plugin_name": "com.sap.hana.di.virtualpackage.hadoop"
|
||||||
|
},
|
||||||
|
"hdbvirtualpackagesparksql": {
|
||||||
|
"plugin_name": "com.sap.hana.di.virtualpackage.sparksql"
|
||||||
|
},
|
||||||
|
"hdbvirtualprocedure": {
|
||||||
|
"plugin_name": "com.sap.hana.di.virtualprocedure"
|
||||||
|
},
|
||||||
|
"hdbvirtualprocedureconfig": {
|
||||||
|
"plugin_name": "com.sap.hana.di.virtualprocedure.config"
|
||||||
|
},
|
||||||
|
"hdbvirtualtable": {
|
||||||
|
"plugin_name": "com.sap.hana.di.virtualtable"
|
||||||
|
},
|
||||||
|
"hdbvirtualtableconfig": {
|
||||||
|
"plugin_name": "com.sap.hana.di.virtualtable.config"
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"plugin_name": "com.sap.hana.di.tabledata.properties"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"plugin_name": "com.sap.hana.di.tabledata.properties"
|
||||||
|
},
|
||||||
|
"txt": {
|
||||||
|
"plugin_name": "com.sap.hana.di.copyonly"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
bookshop/db/undeploy.json
Normal file
5
bookshop/db/undeploy.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
"src/gen/**/*.hdbview",
|
||||||
|
"src/gen/**/*.hdbindex",
|
||||||
|
"src/gen/**/*.hdbconstraint"
|
||||||
|
]
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
async function run() {
|
|
||||||
//debugger
|
|
||||||
//while (true) {}
|
|
||||||
//process.exit()
|
|
||||||
//1.substring()
|
|
||||||
// let res = await specialselect
|
|
||||||
let res = await SELECT.one`title`.from(`Books`).where(`ID=201`)
|
|
||||||
let { title } = res
|
|
||||||
let Author = req.data
|
|
||||||
//await srv.read('Books')
|
|
||||||
|
|
||||||
Author.modifiedBy = "Custom Event handler changed this!"
|
|
||||||
Author.placeOfDeath = " --- Somewhere over " + title + " --- create in Sandbox"
|
|
||||||
//await this.emit("createdAuthor", { Author })
|
|
||||||
return Author
|
|
||||||
}
|
|
||||||
run()
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
function getYear(v) {
|
|
||||||
return parseInt(v.substr(0, 4))
|
|
||||||
}
|
|
||||||
function getMonth(v) {
|
|
||||||
return parseInt(v.substr(5, 2))
|
|
||||||
}
|
|
||||||
function getDay(v) {
|
|
||||||
return parseInt(v.substr(8, 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAge(from, to) {
|
|
||||||
if (from === undefined || from == null) return 0
|
|
||||||
if (to === undefined || to == null) to = new Date().toISOString()
|
|
||||||
let year = getYear(to) - getYear(from) - 1
|
|
||||||
if (
|
|
||||||
getMonth(to) > getMonth(from) ||
|
|
||||||
(getMonth(to) === getMonth(from) && getDay(to) >= getDay(from))
|
|
||||||
) {
|
|
||||||
year++
|
|
||||||
}
|
|
||||||
return year
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
const result_ = Array.isArray(result) ? result : [result]
|
|
||||||
for (const row of result_) {
|
|
||||||
row.age = getAge(row.dateOfBirth, row.dateOfDeath)
|
|
||||||
let res = await SELECT.one`title`.from(`Books`).where({ author_ID: row.ID })
|
|
||||||
if (!res) {
|
|
||||||
res = {}
|
|
||||||
}
|
|
||||||
let { title } = res
|
|
||||||
if (!title) {
|
|
||||||
title = "no Books yet"
|
|
||||||
}
|
|
||||||
row.exampleBook = title
|
|
||||||
//let pub = await SELECT.one`name`.from(`sap_capire_bookshop_Publishers`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run()
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
|
|
||||||
async function run() {
|
|
||||||
const {stock, price, author_ID} = req.data
|
|
||||||
if (stock<0) return req.reject('409', 'Stock must not be negative')
|
|
||||||
if (price<0) return req.reject('409', 'Price must not be negative')
|
|
||||||
let {name} = await SELECT.one`name`.from(`Authors`).where({ID: author_ID})
|
|
||||||
req.data.authorName=name
|
|
||||||
}
|
|
||||||
output = run()
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
const result_ = Array.isArray(result) ? result : [result];
|
|
||||||
for (const row of result_) {
|
|
||||||
if (row.stock > 50) {
|
|
||||||
row.title += " ---Order now for a 10% discount!";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
async function run() {
|
|
||||||
const {author, newName} = req.data
|
|
||||||
let a = await SELECT `name`.from(`Authors`).where({ID: author})
|
|
||||||
if(!a) return req.error (404, `Can't rename a non-existing author`)
|
|
||||||
await UPDATE (`Authors`,author).with ({ name: newName })
|
|
||||||
//await this.emit ('renamedAuthor', { author, newName })
|
|
||||||
output.msg = 'Success'
|
|
||||||
}
|
|
||||||
run()
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
async function run() {
|
|
||||||
let {Author} = req.data
|
|
||||||
Author.placeOfBirth += ' --- modified in custom event'
|
|
||||||
}
|
|
||||||
run()
|
|
||||||
98
bookshop/mta.yaml
Normal file
98
bookshop/mta.yaml
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
---
|
||||||
|
_schema-version: '3.1'
|
||||||
|
ID: capire.bookshop
|
||||||
|
version: 1.0.0
|
||||||
|
description: "A simple self-contained bookshop service."
|
||||||
|
parameters:
|
||||||
|
enable-parallel-deployments: true
|
||||||
|
build-parameters:
|
||||||
|
before-all:
|
||||||
|
- builder: custom
|
||||||
|
commands:
|
||||||
|
- npx -p @sap/cds-dk cds build --production
|
||||||
|
|
||||||
|
modules:
|
||||||
|
- name: bookshop-srv
|
||||||
|
type: nodejs
|
||||||
|
path: gen/srv
|
||||||
|
parameters:
|
||||||
|
buildpack: nodejs_buildpack
|
||||||
|
build-parameters:
|
||||||
|
builder: npm-ci
|
||||||
|
provides:
|
||||||
|
- name: srv-api # required by consumers of CAP services (e.g. approuter)
|
||||||
|
properties:
|
||||||
|
srv-url: ${default-url}
|
||||||
|
- name: mtx-api # potentially required by approuter
|
||||||
|
properties:
|
||||||
|
mtx-url: ${default-url}
|
||||||
|
requires:
|
||||||
|
- name: bookshop-auth
|
||||||
|
- name: bookshop-db
|
||||||
|
- name: bookshop-registry
|
||||||
|
properties:
|
||||||
|
SUBSCRIPTION_URL: ${protocol}://\${tenant_subdomain}-${default-uri}
|
||||||
|
SUBSCRIPTION_URL_REPLACEMENT_RULES: [ [ '-srv', '' ] ]
|
||||||
|
|
||||||
|
- name: bookshop
|
||||||
|
type: approuter.nodejs
|
||||||
|
path: app/ # from cds.env.folders. Consider also cds.env.build.target -> gen/app
|
||||||
|
parameters:
|
||||||
|
keep-existing-routes: true
|
||||||
|
disk-quota: 256M
|
||||||
|
memory: 256M
|
||||||
|
requires:
|
||||||
|
- name: srv-api
|
||||||
|
group: destinations
|
||||||
|
properties:
|
||||||
|
name: srv-api # must be used in xs-app.json as well
|
||||||
|
url: ~{srv-url}
|
||||||
|
forwardAuthToken: true
|
||||||
|
- name: bookshop-auth
|
||||||
|
- name: mtx-api
|
||||||
|
group: destinations
|
||||||
|
properties:
|
||||||
|
name: mtx-api # must be used in xs-app.json as well
|
||||||
|
url: ~{mtx-url}
|
||||||
|
properties:
|
||||||
|
TENANT_HOST_PATTERN: "^(.*)-${default-uri}"
|
||||||
|
|
||||||
|
resources:
|
||||||
|
|
||||||
|
- name: bookshop-auth
|
||||||
|
type: org.cloudfoundry.managed-service
|
||||||
|
parameters:
|
||||||
|
service: xsuaa
|
||||||
|
service-plan: application
|
||||||
|
path: ./xs-security.json
|
||||||
|
config:
|
||||||
|
xsappname: bookshop-${org}-${space}
|
||||||
|
tenant-mode: shared
|
||||||
|
|
||||||
|
- name: bookshop-db
|
||||||
|
type: org.cloudfoundry.managed-service
|
||||||
|
parameters:
|
||||||
|
service: service-manager
|
||||||
|
service-plan: container
|
||||||
|
properties:
|
||||||
|
hdi-service-name: ${service-name}
|
||||||
|
|
||||||
|
- name: bookshop-registry
|
||||||
|
type: org.cloudfoundry.managed-service
|
||||||
|
requires:
|
||||||
|
- name: mtx-api
|
||||||
|
parameters:
|
||||||
|
service: saas-registry
|
||||||
|
service-plan: application
|
||||||
|
config:
|
||||||
|
xsappname: bookshop-${org}-${space}
|
||||||
|
appName: bookshop-${org}-${space}
|
||||||
|
displayName: bookshop
|
||||||
|
description: A simple CAP project.
|
||||||
|
category: 'Category'
|
||||||
|
appUrls:
|
||||||
|
getDependencies: ~{mtx-api/mtx-url}/mtx/v1/provisioning/dependencies
|
||||||
|
onSubscription: ~{mtx-api/mtx-url}/mtx/v1/provisioning/tenant/{tenantId}
|
||||||
|
onSubscriptionAsync: false
|
||||||
|
onUnSubscriptionAsync: false
|
||||||
|
callbackTimeoutMillis: 300000
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
# Base assumption
|
|
||||||
|
|
||||||
Event handlers will always use **publicly available application API's**(services)
|
|
||||||
|
|
||||||
- already done in Sandbox API by overwriting **SELECT**, **UPDATE**, **READ** and **CREATE**
|
|
||||||
|
|
||||||
## Inbound data for validations
|
|
||||||
|
|
||||||
- req.target plus expand on related data
|
|
||||||
- lazy loading on expand
|
|
||||||
- event facade could have an explicit publishing of specific services or documents (e.g. remote services)
|
|
||||||
- CQN Protocol adapter for subsequent reads --> req.data plus application service calls
|
|
||||||
- what is the CDS subset to put in?
|
|
||||||
- req.data + target-rec (proxy, unloaded)
|
|
||||||
- ORM type lazy loading (dereferenced)
|
|
||||||
- application developer could actually provide custom proxies for specific functions
|
|
||||||
- performance impact of multiple accesses to object graph and multiple DB roundtrips
|
|
||||||
- can static code checking or developer annotations influence what is loaded into a graph?
|
|
||||||
- alternative: Stripped-down SELECT limited to req.target and ID
|
|
||||||
- application service only
|
|
||||||
- access rights of user respected
|
|
||||||
- What about to-many relationships? For compositions essential, for associations to be questioned
|
|
||||||
- Application Service Reads
|
|
||||||
- outbound data for changes
|
|
||||||
- call remote services
|
|
||||||
- register new remote services dynamically
|
|
||||||
- CAP provides an API on remote services - connect doesn't need to be done by extension developer
|
|
||||||
- alternative: declarative remote services plumbing with CDS service facade
|
|
||||||
- model looks like static internal services, remote calls done transparently behind the scenes
|
|
||||||
|
|
||||||
-Emit Events
|
|
||||||
|
|
||||||
- choreography of extension points
|
|
||||||
- deep inserts vs. fine grained operations
|
|
||||||
- input validation may be suited for fine grained operations
|
|
||||||
- today not in scope for performance reasons
|
|
||||||
- two different use case: Insert new page to book vs. update order-header with items-constraints in place
|
|
||||||
- reject request, return errors and warnings - suitable for UI, too
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Annotations available:
|
|
||||||
|
|
||||||
Entity level
|
|
||||||
@expression.constraint : [{if: 'expression evaluates to bool'}, on: ['INSERT, UPDATE, DELETE'], error: 'Transaction Rollback and error message', warning: 'Transaction proceeds and warning message']
|
|
||||||
@expression.computed : [{expression: 'ability to access request payload and modify it', on: ['INSERT, UPDATE']}]
|
|
||||||
@event : [{if: 'expression evaluates to bool', on: ['INSERT, UPDATE, DELETE, READ'], when: 'before or after, default before', emit: 'Event Name', to: 'Messaging target, optional'}]
|
|
||||||
@expresion.code :[{file: 'file name', on:['insert', 'update'], when: 'before or after, default before'},
|
|
||||||
{source: 'each => { if (each.stock > 111) {each.title += `-- 11% discount!`; each.price= each.price*0.9}', on:['insert', 'update'], when: 'before or after'}]
|
|
||||||
Atribute Level
|
|
||||||
@assert.constraint : {if: 'stock>=0 OR stock <1000', error: 'i18n/error102'};
|
|
||||||
@event : {if: 'expression evaluates to bool', on: ['INSERT, UPDATE, DELETE, READ'], when: 'before or after', emit: 'Event Name', to: 'Messaging target, optional' }
|
|
||||||
|
|
||||||
Functions available:
|
|
||||||
EXISTS(association target)
|
|
||||||
COUNT,AVG,MIN,MAX,SUM: Composition items, arrays etc
|
|
||||||
OLD: before image
|
|
||||||
EACH: loop over composition items
|
|
||||||
|
|
||||||
Events covered:
|
|
||||||
CRUD --> Longhand and Shorthand supported?
|
|
||||||
Upsert as one event?
|
|
||||||
Before and after:
|
|
||||||
Before can change change request payload and stop transaction
|
|
||||||
After should trigger only asynchronous messages
|
|
||||||
Specific Events for status changes? I think expression based event emitter suffices
|
|
||||||
*/
|
|
||||||
|
|
||||||
//Entity level annotations
|
|
||||||
@expression.constraint : [{if: 'stock>100 AND price>15)', on: ['INSERT', 'UPDATE'], error: 'No Book over price 15 should have more than 100 stock' }, // error, rollback transactions
|
|
||||||
{if: 'stock>90 AND price>15)', on: ['I', 'U'], warning: 'No Book over price 15 should have more than 100 stock' }] //warning, proceed with transaction but report warning back to UI
|
|
||||||
@expression.computed : {expression: 'if(stock>100) then price=price*0.9', on: ['INSERT']} //ability to modify the payload of the request, but nothing beyond it
|
|
||||||
@expresion.code :[{file: 'sap.capire.bookshop-Books-beforeInsert', on:['insert', 'update'], when: 'before'}, //naming can be arbitrary?
|
|
||||||
{source: 'each => { if (each.stock > 111) {each.title += `-- 11% discount!`; each.price= each.price*0.9}', on:['insert', 'update'], when: 'before'}] //alternative
|
|
||||||
@event : { if:'price>200', emit: 'Expensive Book', to: 'RulesEngine'}
|
|
||||||
entity Books : managed {
|
|
||||||
key ID : Integer;
|
|
||||||
title : localized String(111); @event : {if: 'old.title="Hello"', emit: 'Hello changed' } //old refers to before Image. No "to" clause means message is emitted to any subscriber interested
|
|
||||||
descr : localized String(1111);
|
|
||||||
author : Association to Authors @assert.constraint: 'exists(author)'; //function calls need to evaluate to bool
|
|
||||||
genre : Association to Genres;
|
|
||||||
stock : Integer @assert.constraint : {if: 'stock>=0 OR stock <1000', error: 'Stock not within permitted parameters'}; //when operand is used, no auto-insert
|
|
||||||
price : Decimal(9,2) @assert.constraint : '>0'; //insert operand on left side by default
|
|
||||||
currency : Currency;
|
|
||||||
image : LargeBinary @Core.MediaType : 'image/png';
|
|
||||||
stockWorth: Decimal(9,2) @expression.computed : 'stock*price'; //persisted on write. Overhead in runtime, but performance benefit on read. Payload ignored?
|
|
||||||
// stockWorth2 = stock*price; -- long term goal from compiler team, not persisted on write, but calculated on read
|
|
||||||
stockWorth3 : Decimal @expression.computed: 'if (stock*price>1000) then stockWorth3=stock.price else stockworth3=1000'; //which altenative?
|
|
||||||
stockWorth4 : Decimal @expression.computed: {if: '(stock*price>1000)', then: 'stockWorth3=stock.price', else: 'stockworth3=1000'};
|
|
||||||
}
|
|
||||||
//@assert.expression: 'dateOfBirth<dateOfDeath'
|
|
||||||
entity Authors : managed {
|
|
||||||
key ID : Integer;
|
|
||||||
name : String(111);
|
|
||||||
dateOfBirth : Date ;
|
|
||||||
dateOfDeath : Date @expression.constraint: '>dateOfBirth';
|
|
||||||
placeOfBirth : String;
|
|
||||||
placeOfDeath : String;
|
|
||||||
books : Association to many Books on books.author = $self;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Hierarchically organized Code List for Genres */
|
|
||||||
entity Genres : sap.common.CodeList {
|
|
||||||
key ID : Integer;
|
|
||||||
parent : Association to Genres;
|
|
||||||
children : Composition of many Genres on children.parent = $self;
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
4025
bookshop/package-lock.json
generated
Normal file
4025
bookshop/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,10 +10,12 @@
|
|||||||
"index.js"
|
"index.js"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": ">=5.9",
|
"@sap/cds": "^5",
|
||||||
|
"@sap/cds-mtx": "^2",
|
||||||
|
"@sap/xssec": "^3",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"passport": ">=0.4.1",
|
"hdb": "^0.19.0",
|
||||||
"vm2": ">=3.9.9"
|
"passport": "0.4.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"genres": "cds serve test/genres.cds",
|
"genres": "cds serve test/genres.cds",
|
||||||
@@ -22,13 +24,26 @@
|
|||||||
},
|
},
|
||||||
"cds": {
|
"cds": {
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-extensibility" : true,
|
|
||||||
"db": {
|
"db": {
|
||||||
"kind": "sqlite",
|
"kind": "sql"
|
||||||
"credentials": {
|
},
|
||||||
"database": "sqlite.db"
|
"[production]": {
|
||||||
}
|
"db": {
|
||||||
}
|
"kind": "hana-mt"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"kind": "xsuaa"
|
||||||
|
},
|
||||||
|
"multitenancy": true,
|
||||||
|
"approuter": {
|
||||||
|
"kind": "cloudfoundry"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mtx": {
|
||||||
|
"element-prefix": "Z_",
|
||||||
|
"namespace-blocklist": [],
|
||||||
|
"extension-allowlist": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
@@ -1,30 +1,5 @@
|
|||||||
using { sap.capire.bookshop as my } from '../db/schema';
|
using { sap.capire.bookshop as my } from '../db/schema';
|
||||||
|
service AdminService @(requires:'admin') {
|
||||||
service AdminService // @(requires : 'admin')
|
entity Books as projection on my.Books;
|
||||||
{
|
|
||||||
entity Books as projection on my.Books actions {
|
|
||||||
action increaseStock(count : Integer);
|
|
||||||
function stock() returns Integer;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Extensibility : {
|
|
||||||
Fields.Enabled : true,
|
|
||||||
Fields.Quota: 100,
|
|
||||||
Relations.Enabled : false,
|
|
||||||
Annotations.Enabled : true,
|
|
||||||
Logic.Enabled : true,
|
|
||||||
Logic.constraints: true,
|
|
||||||
Logic.calculations: true,
|
|
||||||
Logic.Handler : [create, update, delete, read]
|
|
||||||
}
|
|
||||||
entity Authors as projection on my.Authors;
|
entity Authors as projection on my.Authors;
|
||||||
|
|
||||||
action renameAuthor(author : Authors:ID, newName : String) returns {
|
|
||||||
msg : String
|
|
||||||
};
|
|
||||||
function getStock(book: Books:ID) returns Integer;
|
|
||||||
event newBook : {
|
|
||||||
book : Books:ID;
|
|
||||||
name : Books:title
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,135 +1,12 @@
|
|||||||
const cds = require("@sap/cds")
|
const cds = require('@sap/cds')
|
||||||
//const cds_sandbox = require("sap/cds/sandbox")
|
|
||||||
const { VM, VMScript } = require("vm2")
|
|
||||||
const fs = require("fs")
|
|
||||||
const path = require("path")
|
|
||||||
const { nextTick } = require("process")
|
|
||||||
|
|
||||||
class AdminService extends cds.ApplicationService {
|
module.exports = cds.service.impl (function(){
|
||||||
init() {
|
this.before ('NEW','Authors', genid)
|
||||||
this.after("READ", async (result, req) => {
|
this.before ('NEW','Books', genid)
|
||||||
if (!(result === undefined || result == null)) {
|
|
||||||
const code = getCode(req.target.name, "READ")
|
|
||||||
if (code) {
|
|
||||||
await executeCode.call(this, code, req, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.before("CREATE", async (req) => {
|
|
||||||
const code = getCode(req.target.name, "CREATE")
|
|
||||||
if (code) {
|
|
||||||
await executeCode.call(this, code, req)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.before("UPDATE", async (req) => {
|
|
||||||
const code = getCode(req.target.name, "CREATE")
|
|
||||||
if (code) {
|
|
||||||
await executeCode.call(this, code, req)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.on("*", async (req, next) => {
|
|
||||||
if (!(req.target === undefined || req.target == null)) return next()
|
|
||||||
//ToDo: check whether action or event is part of an extension
|
|
||||||
// DO NOT OVERWRITE EXISTING Action Implementations!
|
|
||||||
// evaluate: Can we augment action implementation with super.next?
|
|
||||||
if (req.constructor.name === "EventMessage") {
|
|
||||||
const code = getCode(req.event, "ON")
|
|
||||||
if (code) {
|
|
||||||
await executeCode.call(this, code, req)
|
|
||||||
}
|
|
||||||
} else if (req.constructor.name === "ODataRequest") {
|
|
||||||
var output = {}
|
|
||||||
const code = getCode(this.name + "." + req.event, "ON")
|
|
||||||
if (code) {
|
|
||||||
await executeCode.call(this, code, req, {}, output)
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
//ToDo: Prefix for Service not in event emitter
|
|
||||||
this.before("CREATE", "Authors", async (req) => {
|
|
||||||
let Author = req.data
|
|
||||||
await this.emit("createdAuthor", { Author })
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
return super.init()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var counter = 1;
|
|
||||||
|
|
||||||
function newLabel() {return "VM2 - req: " + counter++}
|
|
||||||
|
|
||||||
//should only work in local exection (cds watch)
|
|
||||||
// alternative: Upon Bootstrapping, merge files into CSN
|
|
||||||
function getCodeFromFile(name, operation) {
|
|
||||||
const filename = name + "." + operation + ".js"
|
|
||||||
const file = path.join(__dirname, "..", "handlers", filename)
|
|
||||||
try {
|
|
||||||
const code = fs.readFileSync(file, "utf8")
|
|
||||||
return code
|
|
||||||
} catch (error) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//after push this should be the only thing that works
|
|
||||||
function getCodeFromAnnotation(name, operation) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCode(name, operation) {
|
|
||||||
let code=getCodeFromAnnotation(name, operation)
|
|
||||||
if (code==="") {code=getCodeFromFile(name, operation)}
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
function scanCode(code) {
|
|
||||||
//ESLINT
|
|
||||||
}
|
|
||||||
|
|
||||||
async function executeCode(code, req, result, output) {
|
|
||||||
const srv = this
|
|
||||||
const label=newLabel()
|
|
||||||
console.time(label)
|
|
||||||
const vm = new VM({
|
|
||||||
console: "inherit",
|
|
||||||
timeout: 500,
|
|
||||||
allowAsync: true,
|
|
||||||
sandbox: { req, //todo: isolate req.data, req.reject, req.error, req.message
|
|
||||||
result, //important for READ
|
|
||||||
output, //used for Action Implementation
|
|
||||||
SELECT : (class extends require('@sap/cds/lib/ql/SELECT') {then(r,e) {return srv.run(this).then(r,e)}})._api(),
|
|
||||||
INSERT : (class extends require('@sap/cds/lib/ql/INSERT') {then(r,e) {return srv.run(this).then(r,e)}})._api(),
|
|
||||||
UPDATE : (class extends require('@sap/cds/lib/ql/UPDATE') {then(r,e) {return srv.run(this).then(r,e)}})._api(),
|
|
||||||
CREATE : (class extends require('@sap/cds/lib/ql/CREATE') {then(r,e) {return srv.run(this).then(r,e)}})._api(),
|
|
||||||
//srv: this,
|
|
||||||
JSON },
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
await vm.run(code)
|
|
||||||
return output
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
req.reject("409", "Error in VM")
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
console.timeEnd(label)
|
|
||||||
}
|
|
||||||
// console.log(req.data)
|
|
||||||
}
|
|
||||||
/** Generate primary keys for target entity in request */
|
/** Generate primary keys for target entity in request */
|
||||||
async function genid (req) {
|
async function genid (req) {
|
||||||
const { ID } = await cds
|
const {ID} = await cds.tx(req).run (SELECT.one.from(req.target).columns('max(ID) as ID'))
|
||||||
.tx(req)
|
req.data.ID = ID - ID % 100 + 100 + 1
|
||||||
.run(SELECT.one.from(req.target).columns("max(ID) as ID"))
|
|
||||||
req.data.ID = ID - (ID % 100) + 100 + 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { AdminService }
|
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ service CatalogService @(path:'/browse') {
|
|||||||
author.name as author
|
author.name as author
|
||||||
} excluding { createdBy, modifiedBy };
|
} excluding { createdBy, modifiedBy };
|
||||||
|
|
||||||
@readonly entity Publishers as projection on my.Publishers;
|
|
||||||
|
|
||||||
@requires: 'authenticated-user'
|
@requires: 'authenticated-user'
|
||||||
action submitOrder ( book: Books:ID, quantity: Integer ) returns { stock: Integer };
|
action submitOrder ( book: Books:ID, quantity: Integer ) returns { stock: Integer };
|
||||||
event OrderedBook : { book: Books:ID; quantity: Integer; buyer: String };
|
event OrderedBook : { book: Books:ID; quantity: Integer; buyer: String };
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
//this file is machine-created during cds.build
|
|
||||||
namespace sap.capire.bookshop; //> important for reflection
|
|
||||||
using from '../db/schema';
|
|
||||||
using from '../srv/cat-service';
|
|
||||||
using from '../srv/admin-service';
|
|
||||||
|
|
||||||
annotate AdminService.Authors with @extension.logic: [{when: 'CREATE', code: 'async function run() {\r\n \/\/debugger\r\n \/\/while (true) {}\r\n \/\/process.exit()\r\n \/\/1.substring()\r\n \/\/ let res = await specialselect\r\n let res = await SELECT.one`title`.from(`Books`).where(`ID=201`)\r\n let { title } = res\r\n let Author = req.data\r\n Author.modifiedBy = \"Custom Event handler changed this!\"\r\n Author.placeOfDeath = \" --- Somewhere over \" + title + \" --- create in Sandbox\"\r\n \/\/await this.emit(\"createdAuthor\", { Author })\r\n return Author\r\n}\r\nrun()\r\n'},
|
|
||||||
{when: 'READ', code: 'function getYear(v) {\r\n return parseInt(v.substr(0, 4))\r\n}\r\nfunction getMonth(v) {\r\n return parseInt(v.substr(5, 2))\r\n}\r\nfunction getDay(v) {\r\n return parseInt(v.substr(8, 2))\r\n}\r\n\r\nfunction getAge(from, to) {\r\n if (from === undefined || from == null) return 0\r\n if (to === undefined || to == null) to = new Date().toISOString()\r\n let year = getYear(to) - getYear(from) - 1\r\n if (\r\n getMonth(to) > getMonth(from) ||\r\n (getMonth(to) === getMonth(from) && getDay(to) >= getDay(from))\r\n ) {\r\n year++\r\n }\r\n return year\r\n}\r\n\r\nconst result_ = Array.isArray(result) ? result : [result]\r\nfor (const row of result_) {\r\n row.modifiedBy += \" --- read in sandbox\"\r\n row.age = getAge(row.dateOfBirth, row.dateOfDeath)\r\n}'}
|
|
||||||
];
|
|
||||||
annotate AdminService.Books with @extension.logic;
|
|
||||||
annotate CatalogService.ListOfBooks with @extension.logic;
|
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
/**
|
|
||||||
* Exposes user information
|
|
||||||
*/
|
|
||||||
@requires : 'authenticated-user'
|
@requires : 'authenticated-user'
|
||||||
service UserService {
|
service UserService {
|
||||||
|
|
||||||
/**
|
@odata.singleton
|
||||||
* The current user
|
entity User {
|
||||||
*/
|
ID : String;
|
||||||
@odata.singleton entity me {
|
|
||||||
id : String; // user id
|
|
||||||
locale : String;
|
locale : String;
|
||||||
tenant : String;
|
tenant : String;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
const cds = require('@sap/cds')
|
const cds = require('@sap/cds');
|
||||||
|
|
||||||
module.exports = cds.service.impl((srv) => {
|
module.exports = cds.service.impl((srv) => {
|
||||||
srv.on('READ', 'me', ({ tenant, user, locale }) => ({ id: user.id, locale, tenant }))
|
srv.on('READ', 'User', ({ user }) => {
|
||||||
})
|
return {
|
||||||
|
ID: user.id,
|
||||||
|
locale: user.locale,
|
||||||
|
tenant: user.tenant,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,46 +1,10 @@
|
|||||||
@server = http://localhost:4004
|
@server = http://localhost:4004
|
||||||
@me = Authorization: Basic {{$processEnv USER}}:
|
@me = Authorization: Basic {{$processEnv USER}}:
|
||||||
@id = 2000
|
|
||||||
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
|
||||||
# Fetch Authors
|
|
||||||
GET {{server}}/admin/Authors
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
|
||||||
# Fetch one Author
|
|
||||||
GET {{server}}/admin/Authors({{id}})
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
|
||||||
# Create Author
|
|
||||||
POST {{server}}/admin/Authors
|
|
||||||
Content-Type: application/json;IEEE754Compatible=true
|
|
||||||
|
|
||||||
{
|
|
||||||
"ID": {{id}},
|
|
||||||
"name": "Nick",
|
|
||||||
"placeOfBirth": "Somewhere",
|
|
||||||
"placeOfDeath": "over the Rainbox",
|
|
||||||
"dateOfBirth" : "1975-05-27"
|
|
||||||
}
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
|
||||||
# rename author via unbound action
|
|
||||||
POST {{server}}/admin/renameAuthor
|
|
||||||
Content-Type: application/json
|
|
||||||
{{me}}
|
|
||||||
|
|
||||||
{ "author":{{id}}, "newName":"Super Nick" }
|
|
||||||
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
### ------------------------------------------------------------------------
|
||||||
# Get service info
|
# Get service info
|
||||||
GET {{server}}/admin
|
GET {{server}}/browse
|
||||||
{{me}}
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
|
||||||
# Get $metadata document
|
|
||||||
GET {{server}}/admin/$metadata
|
|
||||||
{{me}}
|
{{me}}
|
||||||
|
|
||||||
|
|
||||||
@@ -52,19 +16,34 @@ GET {{server}}/browse/$metadata
|
|||||||
|
|
||||||
### ------------------------------------------------------------------------
|
### ------------------------------------------------------------------------
|
||||||
# Browse Books as any user
|
# Browse Books as any user
|
||||||
GET {{server}}/browse/ListOfBooks?
|
GET {{server}}/browse/Books?
|
||||||
# &$select=title,stock
|
# &$select=title,stock
|
||||||
&$expand=genre
|
# &$expand=currency
|
||||||
# &sap-language=de
|
# &sap-language=de
|
||||||
{{me}}
|
{{me}}
|
||||||
|
|
||||||
|
|
||||||
|
### ------------------------------------------------------------------------
|
||||||
|
# Fetch Authors as admin
|
||||||
|
GET {{server}}/admin/Authors?
|
||||||
|
# &$select=name,dateOfBirth,placeOfBirth
|
||||||
|
# &$expand=books($select=title;$expand=currency)
|
||||||
|
# &$filter=ID eq 101
|
||||||
|
# &sap-language=de
|
||||||
|
Authorization: Basic alice:
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
### ------------------------------------------------------------------------
|
||||||
# Fetch Books as admin
|
# Create Author
|
||||||
GET {{server}}/admin/Books
|
POST {{server}}/admin/Authors
|
||||||
|
Content-Type: application/json;IEEE754Compatible=true
|
||||||
|
Authorization: Basic alice:
|
||||||
|
|
||||||
|
{
|
||||||
|
"ID": 112,
|
||||||
|
"name": "Shakespeeeeere",
|
||||||
|
"age": 22
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
### ------------------------------------------------------------------------
|
||||||
# Create book
|
# Create book
|
||||||
@@ -73,12 +52,12 @@ Content-Type: application/json;IEEE754Compatible=true
|
|||||||
Authorization: Basic alice:
|
Authorization: Basic alice:
|
||||||
|
|
||||||
{
|
{
|
||||||
"ID": 16,
|
"ID": 2,
|
||||||
"title": "Deh4",
|
"title": "Poems : Pocket Poets",
|
||||||
"descr": "The Everyman's Library Pocket Poets hardcover series is popular for its compact size and reasonable price which does not compromise content. Poems: Bronte contains poems that demonstrate a sensibility elemental in its force with an imaginative discipline and flexibility of the highest order. Also included are an Editor's Note and an index of first lines.",
|
"descr": "The Everyman's Library Pocket Poets hardcover series is popular for its compact size and reasonable price which does not compromise content. Poems: Bronte contains poems that demonstrate a sensibility elemental in its force with an imaginative discipline and flexibility of the highest order. Also included are an Editor's Note and an index of first lines.",
|
||||||
"author": { "ID": 101 },
|
"author": { "ID": 101 },
|
||||||
"genre": { "ID": 12 },
|
"genre": { "ID": 12 },
|
||||||
"stock": -100,
|
"stock": 5,
|
||||||
"price": "12.05",
|
"price": "12.05",
|
||||||
"currency": { "code": "USD" }
|
"currency": { "code": "USD" }
|
||||||
}
|
}
|
||||||
|
|||||||
70
bookshop/xs-security.json
Normal file
70
bookshop/xs-security.json
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.admin",
|
||||||
|
"description": "admin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.MtxDiagnose",
|
||||||
|
"description": "Diagnose MTX"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.mtcallback",
|
||||||
|
"description": "Subscribe to applications",
|
||||||
|
"grant-as-authority-to-apps": [
|
||||||
|
"$XSAPPNAME(application,sap-provisioning,tenant-onboarding)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.mtdeployment",
|
||||||
|
"description": "Deploy applications"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.ExtendCDS",
|
||||||
|
"description": "Extend CDS applications"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.ExtendCDSdelete",
|
||||||
|
"description": "Extend CDS applications with undeployments"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attributes": [],
|
||||||
|
"role-templates": [
|
||||||
|
{
|
||||||
|
"name": "admin",
|
||||||
|
"description": "generated",
|
||||||
|
"scope-references": [
|
||||||
|
"$XSAPPNAME.admin"
|
||||||
|
],
|
||||||
|
"attribute-references": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MultitenancyAdministrator",
|
||||||
|
"description": "Administrate multitenant applications",
|
||||||
|
"scope-references": [
|
||||||
|
"$XSAPPNAME.MtxDiagnose",
|
||||||
|
"$XSAPPNAME.mtdeployment",
|
||||||
|
"$XSAPPNAME.mtcallback"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ExtensionDeveloper",
|
||||||
|
"description": "Extend application",
|
||||||
|
"scope-references": [
|
||||||
|
"$XSAPPNAME.ExtendCDS"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ExtensionDeveloperUndeploy",
|
||||||
|
"description": "Undeploy extension",
|
||||||
|
"scope-references": [
|
||||||
|
"$XSAPPNAME.ExtendCDSdelete"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"authorities": [
|
||||||
|
"$XSAPPNAME.MtxDiagnose",
|
||||||
|
"$XSAPPNAME.mtdeployment",
|
||||||
|
"$XSAPPNAME.mtcallback"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
"@capire/orders": "*",
|
"@capire/orders": "*",
|
||||||
"@capire/common": "*",
|
"@capire/common": "*",
|
||||||
"@capire/data-viewer": "*",
|
"@capire/data-viewer": "*",
|
||||||
"@sap/cds": ">=5",
|
"@sap/cds": "^5",
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1"
|
||||||
},
|
},
|
||||||
"cds": {
|
"cds": {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "A generic browser for data",
|
"description": "A generic browser for data",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": ">=5.0.4"
|
"@sap/cds": "^5.0.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"app",
|
"app",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ annotate AdminService.Authors with @(UI : {
|
|||||||
|
|
||||||
// Workaround to avoid errors for unknown db-specific calculated fields above
|
// Workaround to avoid errors for unknown db-specific calculated fields above
|
||||||
extend sap.capire.bookshop.Authors with {
|
extend sap.capire.bookshop.Authors with {
|
||||||
//virtual age : Integer;
|
virtual age : Integer;
|
||||||
virtual lifetime : String;
|
virtual lifetime : String;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dataSources": {
|
"dataSources": {
|
||||||
"AdminService": {
|
"AdminService": {
|
||||||
"uri": "admin/",
|
"uri": "/admin/",
|
||||||
"type": "OData",
|
"type": "OData",
|
||||||
"settings": {
|
"settings": {
|
||||||
"odataVersion": "4.0"
|
"odataVersion": "4.0"
|
||||||
@@ -50,10 +50,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sap.ui5": {
|
"sap.ui5": {
|
||||||
"flexEnabled": true,
|
|
||||||
"config": {
|
|
||||||
"experimentalCAPScenario": true
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minUI5Version": "1.81.0",
|
"minUI5Version": "1.81.0",
|
||||||
"libs": {
|
"libs": {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"i18n": "i18n/i18n.properties",
|
"i18n": "i18n/i18n.properties",
|
||||||
"dataSources": {
|
"dataSources": {
|
||||||
"AdminService": {
|
"AdminService": {
|
||||||
"uri": "admin/",
|
"uri": "/admin/",
|
||||||
"type": "OData",
|
"type": "OData",
|
||||||
"settings": {
|
"settings": {
|
||||||
"odataVersion": "4.0"
|
"odataVersion": "4.0"
|
||||||
@@ -22,10 +22,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sap.ui5": {
|
"sap.ui5": {
|
||||||
"flexEnabled": true,
|
|
||||||
"config": {
|
|
||||||
"experimentalCAPScenario": true
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"libs": {
|
"libs": {
|
||||||
"sap.fe.templates": {}
|
"sap.fe.templates": {}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dataSources": {
|
"dataSources": {
|
||||||
"CatalogService": {
|
"CatalogService": {
|
||||||
"uri": "browse/",
|
"uri": "/browse/",
|
||||||
"type": "OData",
|
"type": "OData",
|
||||||
"settings": {
|
"settings": {
|
||||||
"odataVersion": "4.0"
|
"odataVersion": "4.0"
|
||||||
@@ -53,10 +53,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sap.ui5": {
|
"sap.ui5": {
|
||||||
"flexEnabled": true,
|
|
||||||
"config": {
|
|
||||||
"experimentalCAPScenario": true
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minUI5Version": "1.81.0",
|
"minUI5Version": "1.81.0",
|
||||||
"libs": {
|
"libs": {
|
||||||
|
|||||||
@@ -10,21 +10,7 @@
|
|||||||
<script>
|
<script>
|
||||||
window["sap-ushell-config"] = {
|
window["sap-ushell-config"] = {
|
||||||
defaultRenderer: "fiori2",
|
defaultRenderer: "fiori2",
|
||||||
applications: {},
|
applications: {}
|
||||||
bootstrapPlugins: {
|
|
||||||
RuntimeAuthoringPlugin: {
|
|
||||||
component: "sap.ushell.plugins.rta",
|
|
||||||
config: {
|
|
||||||
validateAppVersion: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PersonalizePlugin: {
|
|
||||||
component: "sap.ushell.plugins.rta-personalize",
|
|
||||||
config: {
|
|
||||||
validateAppVersion: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -36,11 +22,7 @@
|
|||||||
data-sap-ui-frameOptions="allow"
|
data-sap-ui-frameOptions="allow"
|
||||||
></script>
|
></script>
|
||||||
<script>
|
<script>
|
||||||
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"));
|
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
|
||||||
sap.ui
|
|
||||||
.getCore()
|
|
||||||
.getConfiguration()
|
|
||||||
.setFlexibilityServices([{ connector: "SessionStorageConnector" }]);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capire/bookstore": "*",
|
"@capire/bookstore": "*",
|
||||||
"@sap/cds": ">=5",
|
"@sap/cds": "^5",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"passport": ">=0.4.1"
|
"passport": "^0.4.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cds run --in-memory?",
|
"start": "cds run --in-memory?",
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"cds": {
|
"cds": {
|
||||||
"requires": {
|
"requires": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"kind": "dummy-auth"
|
"strategy": "dummy"
|
||||||
},
|
},
|
||||||
"ReviewsService": {
|
"ReviewsService": {
|
||||||
"kind": "odata",
|
"kind": "odata",
|
||||||
@@ -36,10 +36,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"db": {
|
"db": {
|
||||||
"kind": "sqlite",
|
"kind": "sql"
|
||||||
"credentials": {
|
|
||||||
"database": "sqlite.db"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"db-ext": {
|
"db-ext": {
|
||||||
"[development]": {
|
"[development]": {
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
"start:ts": "cds-ts serve srv/world.cds"
|
"start:ts": "cds-ts serve srv/world.cds"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": ">=5.0.4"
|
"@sap/cds": "^5.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "*",
|
"@types/jest": "^27.0.2",
|
||||||
"@types/node": "*",
|
"@types/node": "^16.11.6",
|
||||||
"ts-jest": "^27.0.2",
|
"ts-jest": "^27.0.2",
|
||||||
"typescript": "^4.3.5"
|
"typescript": "^4.3.5"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capire/common": "*",
|
"@capire/common": "*",
|
||||||
"@sap/cds": ">=5"
|
"@sap/cds": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
11561
package-lock.json
generated
11561
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,14 +14,14 @@
|
|||||||
"@capire/media": "./media",
|
"@capire/media": "./media",
|
||||||
"@capire/orders": "./orders",
|
"@capire/orders": "./orders",
|
||||||
"@capire/reviews": "./reviews",
|
"@capire/reviews": "./reviews",
|
||||||
"@sap/cds": ">=5.5.3"
|
"@sap/cds": "^5.5.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "^4.3.4",
|
"chai": "^4.3.4",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
"chai-subset": "^1.6.0",
|
"chai-subset": "^1.6.0",
|
||||||
"semver": "^7",
|
"semver": "^7",
|
||||||
"sqlite3": "^5"
|
"sqlite3": "npm:@mendix/sqlite3@^5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules",
|
"cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"index.cds"
|
"index.cds"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": ">=5",
|
"@sap/cds": "^5",
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1"
|
||||||
},
|
},
|
||||||
"cds": {
|
"cds": {
|
||||||
|
|||||||
@@ -81,8 +81,6 @@ describe('cds.ql → cqn', () => {
|
|||||||
.to.eql(SELECT('Foo','Boo').from('Bar'))
|
.to.eql(SELECT('Foo','Boo').from('Bar'))
|
||||||
.to.eql(SELECT(['Foo','Boo']).from('Bar'))
|
.to.eql(SELECT(['Foo','Boo']).from('Bar'))
|
||||||
.to.eql(SELECT `Bar` .columns `Foo, Boo`)
|
.to.eql(SELECT `Bar` .columns `Foo, Boo`)
|
||||||
.to.eql(SELECT `Bar` .columns `{ Foo, Boo }`)
|
|
||||||
.to.eql(SELECT `Bar` .columns ('{ Foo, Boo }'))
|
|
||||||
.to.eql(SELECT `Bar` .columns ('Foo','Boo'))
|
.to.eql(SELECT `Bar` .columns ('Foo','Boo'))
|
||||||
.to.eql(SELECT `Bar` .columns (['Foo','Boo']))
|
.to.eql(SELECT `Bar` .columns (['Foo','Boo']))
|
||||||
.to.eql(SELECT.from `Bar` .columns ('Foo','Boo'))
|
.to.eql(SELECT.from `Bar` .columns ('Foo','Boo'))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds/lib')
|
||||||
const { expect } = cds.test ('@capire/bookshop')
|
const { expect } = cds.test ('@capire/bookshop')
|
||||||
|
|
||||||
describe('cap/samples - Consuming Services locally', () => {
|
describe('Consuming Services locally', () => {
|
||||||
//
|
//
|
||||||
it('bootstrapped the database successfully', ()=>{
|
it('bootstrapped the database successfully', ()=>{
|
||||||
const { AdminService } = cds.services
|
const { AdminService } = cds.services
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const { GET, POST, expect } = cds.test(__dirname+'/../bookshop')
|
|||||||
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
||||||
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
||||||
|
|
||||||
describe('cap/samples - Custom Handlers', () => {
|
describe('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 POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds/lib')
|
||||||
const { GET, expect } = cds.test (__dirname+'/../hello')
|
const { GET, expect } = cds.test (__dirname+'/../hello')
|
||||||
|
|
||||||
describe('cap/samples - Hello world!', () => {
|
describe('Hello world!', () => {
|
||||||
|
|
||||||
it('should say hello with class impl', async () => {
|
it('should say hello with class impl', async () => {
|
||||||
const {data} = await GET `/say/hello(to='world')`
|
const {data} = await GET `/say/hello(to='world')`
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const model = cds.compile.to.csn (`
|
|||||||
const {Categories:Cats} = model.definitions
|
const {Categories:Cats} = model.definitions
|
||||||
|
|
||||||
|
|
||||||
describe('cap/samples - Hierarchical Data', ()=>{
|
describe('Hierarchical Data', ()=>{
|
||||||
|
|
||||||
before ('bootstrap sqlite in-memory db...', async()=>{
|
before ('bootstrap sqlite in-memory db...', async()=>{
|
||||||
await cds.deploy (model) .to ('sqlite::memory:')
|
await cds.deploy (model) .to ('sqlite::memory:')
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const { GET, expect, cds } = require('@sap/cds/lib').test (__dirname)
|
|||||||
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
||||||
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
||||||
|
|
||||||
describe('cap/samples - Localized Data', () => {
|
describe('Localized Data', () => {
|
||||||
|
|
||||||
it('serves localized $metadata documents', async () => {
|
it('serves localized $metadata documents', async () => {
|
||||||
const { data } = await GET`/browse/$metadata?sap-language=de`
|
const { data } = await GET`/browse/$metadata?sap-language=de`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const _model = '@capire/reviews'
|
|||||||
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
||||||
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
||||||
|
|
||||||
describe('cap/samples - Messaging', ()=>{
|
describe('Messaging', ()=>{
|
||||||
|
|
||||||
it ('should bootstrap sqlite in-memory db', async()=>{
|
it ('should bootstrap sqlite in-memory db', async()=>{
|
||||||
const db = await cds.deploy (_model) .to ('sqlite::memory:')
|
const db = await cds.deploy (_model) .to ('sqlite::memory:')
|
||||||
|
|||||||
@@ -1,48 +1,9 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds/lib')
|
||||||
const { GET, expect, axios } = cds.test ('@capire/bookshop')
|
const { GET, expect } = cds.test ('@capire/bookshop')
|
||||||
axios.defaults.auth = { username: 'alice', password: 'admin' }
|
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
||||||
|
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
||||||
|
|
||||||
describe('cap/samples - Bookshop APIs', () => {
|
describe('OData Protocol', () => {
|
||||||
|
|
||||||
// Genres
|
|
||||||
const Drama = {
|
|
||||||
"name": "Drama",
|
|
||||||
"descr": null,
|
|
||||||
"ID": 11,
|
|
||||||
"parent_ID": 10
|
|
||||||
}
|
|
||||||
const Mystery = {
|
|
||||||
"name": "Mystery",
|
|
||||||
"descr": null,
|
|
||||||
"ID": 16,
|
|
||||||
"parent_ID": 10
|
|
||||||
}
|
|
||||||
const Fantasy = {
|
|
||||||
"name": "Fantasy",
|
|
||||||
"descr": null,
|
|
||||||
"ID": 13,
|
|
||||||
"parent_ID": 10
|
|
||||||
}
|
|
||||||
|
|
||||||
// Currencies
|
|
||||||
const GBP = {
|
|
||||||
"name": "British Pound",
|
|
||||||
"descr": null,
|
|
||||||
"code": "GBP",
|
|
||||||
"symbol": "£"
|
|
||||||
}
|
|
||||||
const USD = {
|
|
||||||
"name": "US Dollar",
|
|
||||||
"descr": null,
|
|
||||||
"code": "USD",
|
|
||||||
"symbol": "$"
|
|
||||||
}
|
|
||||||
const JPY = {
|
|
||||||
"name": "Yen",
|
|
||||||
"descr": null,
|
|
||||||
"code": "JPY",
|
|
||||||
"symbol": "¥"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
it('serves $metadata documents in v4', async () => {
|
it('serves $metadata documents in v4', async () => {
|
||||||
@@ -56,16 +17,6 @@ describe('cap/samples - Bookshop APIs', () => {
|
|||||||
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>')
|
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('serves ListOfBooks?$expand=genre,currency', async () => {
|
|
||||||
const { data } = await GET `/browse/ListOfBooks ${{
|
|
||||||
params: { $search: 'Po', $select: `title,author`, $expand:`genre,currency` },
|
|
||||||
}}`
|
|
||||||
expect(data.value).to.eql([
|
|
||||||
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe', genre:Mystery, currency:USD },
|
|
||||||
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe', genre:Mystery, currency:USD },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('supports $search in multiple fields', async () => {
|
it('supports $search in multiple fields', async () => {
|
||||||
const { data } = await GET `/browse/Books ${{
|
const { data } = await GET `/browse/Books ${{
|
||||||
params: { $search: 'Po', $select: `title,author` },
|
params: { $search: 'Po', $select: `title,author` },
|
||||||
@@ -124,16 +75,4 @@ describe('cap/samples - Bookshop APIs', () => {
|
|||||||
{ ID: 271, title: 'Catweazle' },
|
{ ID: 271, title: 'Catweazle' },
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('serves user info', async () => {
|
|
||||||
{
|
|
||||||
const { data } = await GET (`/user/me`)
|
|
||||||
expect(data).to.containSubset({ id: 'alice', locale:'en', tenant: null })
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const { data } = await GET (`/user/me`, {auth: { username: 'joe' }})
|
|
||||||
expect(data).to.containSubset({ id: 'joe', locale:'en', tenant: null })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const { resolve } = require('path')
|
|||||||
const verbose = process.env.CDS_TEST_VERBOSE
|
const verbose = process.env.CDS_TEST_VERBOSE
|
||||||
// ||true
|
// ||true
|
||||||
|
|
||||||
describe('cap/samples - Local NPM registry', () => {
|
describe('Local NPM registry', () => {
|
||||||
let registry
|
let registry
|
||||||
let axios
|
let axios
|
||||||
const cwd = resolve(__dirname, '..')
|
const cwd = resolve(__dirname, '..')
|
||||||
@@ -20,7 +20,7 @@ describe('cap/samples - Local NPM registry', () => {
|
|||||||
|
|
||||||
after(() => { registry.kill() })
|
after(() => { registry.kill() })
|
||||||
|
|
||||||
for (const mod of ['bookshop', 'data-viewer', 'fiori','orders','reviews']) {
|
for (const mod of ['bookshop','fiori','orders','reviews']) {
|
||||||
it(`should serve ${mod}`, async () => {
|
it(`should serve ${mod}`, async () => {
|
||||||
const resp = await axios.get(`/@capire/${mod}`)
|
const resp = await axios.get(`/@capire/${mod}`)
|
||||||
expect(resp.data).to.containSubset({name: `@capire/${mod}`, versions:{}})
|
expect(resp.data).to.containSubset({name: `@capire/${mod}`, versions:{}})
|
||||||
|
|||||||
Reference in New Issue
Block a user