Compare commits
150 Commits
enable-fix
...
multitenan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cc9fe50c4 | ||
|
|
c479d86aab | ||
|
|
2a933ead92 | ||
|
|
88c05a9c7f | ||
|
|
0a8ab2c137 | ||
|
|
aadfd9f7a1 | ||
|
|
8e2e899722 | ||
|
|
d516ffdb06 | ||
|
|
ab87eb6ee9 | ||
|
|
b379822569 | ||
|
|
46aec07596 | ||
|
|
35df5e1552 | ||
|
|
cf717c4cf3 | ||
|
|
8bc6343223 | ||
|
|
33c21f6f3a | ||
|
|
7810585a0f | ||
|
|
68c8b95554 | ||
|
|
fac88be2bf | ||
|
|
b59a02588c | ||
|
|
0f02e25b1e | ||
|
|
be46fff7e9 | ||
|
|
2814486bfd | ||
|
|
7f75401346 | ||
|
|
1d91a8e2d9 | ||
|
|
60ba27ff3c | ||
|
|
b7ff0cd100 | ||
|
|
67038e7fe1 | ||
|
|
ac895a508a | ||
|
|
172aab7f8d | ||
|
|
82d1891ece | ||
|
|
01a528ba83 | ||
|
|
804887e208 | ||
|
|
0972858cbb | ||
|
|
d57974fb5e | ||
|
|
e14a4e8c40 | ||
|
|
483a5f2696 | ||
|
|
f952764b48 | ||
|
|
70d132fed9 | ||
|
|
e3551dc917 | ||
|
|
0f47b9ae8e | ||
|
|
76bde6ff41 | ||
|
|
09aa318879 | ||
|
|
81f762d7be | ||
|
|
309c76caae | ||
|
|
13fc17a760 | ||
|
|
c458bf0f47 | ||
|
|
0d8abf71cb | ||
|
|
dc5cc5e607 | ||
|
|
594043e55c | ||
|
|
2536f36596 | ||
|
|
5f160c0927 | ||
|
|
0c4658a3ef | ||
|
|
33cd70065a | ||
|
|
450577d5a7 | ||
|
|
5a23ba0f76 | ||
|
|
064ce32b8a | ||
|
|
2c064d0200 | ||
|
|
51ee6d167c | ||
|
|
049345a39c | ||
|
|
a7e0cf61a3 | ||
|
|
1b0462f52e | ||
|
|
2bae86079e | ||
|
|
dcab44ab68 | ||
|
|
9f0629eef1 | ||
|
|
f6c938457f | ||
|
|
647ac72588 | ||
|
|
2975038696 | ||
|
|
861094cc83 | ||
|
|
2368913612 | ||
|
|
22ece2e586 | ||
|
|
ae788414f8 | ||
|
|
ebc7017ded | ||
|
|
876ff94782 | ||
|
|
76d1b1865b | ||
|
|
85dd8558f4 | ||
|
|
d0d95f3c42 | ||
|
|
c46a82d0f8 | ||
|
|
7654012292 | ||
|
|
65c8c82f74 | ||
|
|
d368eb2ff5 | ||
|
|
3cbb199870 | ||
|
|
b51a08bf4e | ||
|
|
b31efc8083 | ||
|
|
6669b983b1 | ||
|
|
b6e5a2fced | ||
|
|
6de09e0940 | ||
|
|
b6f3914d79 | ||
|
|
28402c58b3 | ||
|
|
77de0e445e | ||
|
|
a037d92c97 | ||
|
|
b5031588ce | ||
|
|
85319d9e8d | ||
|
|
39872200ae | ||
|
|
6a4af929f1 | ||
|
|
5b966c503c | ||
|
|
75628b6096 | ||
|
|
c12e516f5d | ||
|
|
01073fd6a5 | ||
|
|
dc72442764 | ||
|
|
7e04f50852 | ||
|
|
ea6e274810 | ||
|
|
86e5c429bd | ||
|
|
f32398ba8d | ||
|
|
684c2d53f1 | ||
|
|
b4594e23c5 | ||
|
|
b6028721af | ||
|
|
e15a6192b6 | ||
|
|
d1eb14f638 | ||
|
|
394a8b5d12 | ||
|
|
dae8e96fe1 | ||
|
|
8f01bf911e | ||
|
|
932f56812c | ||
|
|
d80084bfb7 | ||
|
|
f2c37ec162 | ||
|
|
d0d08b1ee1 | ||
|
|
ffaec7aa07 | ||
|
|
1587521bd3 | ||
|
|
c04c423c0d | ||
|
|
7b5c60c3c7 | ||
|
|
8317503b20 | ||
|
|
f6c838e6ea | ||
|
|
5d64ca3555 | ||
|
|
392106d44a | ||
|
|
df55110b9b | ||
|
|
d9a06d16f1 | ||
|
|
c424517770 | ||
|
|
363e9aa9a8 | ||
|
|
7131c13500 | ||
|
|
934a00eff1 | ||
|
|
a238390b73 | ||
|
|
54e170ef88 | ||
|
|
df3cfbb956 | ||
|
|
14756e47f7 | ||
|
|
90edc4289f | ||
|
|
7ae618d803 | ||
|
|
e19447b700 | ||
|
|
ab18c12a69 | ||
|
|
a456eae8ba | ||
|
|
d8308fe7a3 | ||
|
|
a75f8bdc45 | ||
|
|
a71aaf75a1 | ||
|
|
6fdd91b8c8 | ||
|
|
62c3969185 | ||
|
|
2d608cd882 | ||
|
|
ae8fe083ed | ||
|
|
6b3d9db4e9 | ||
|
|
332fae3761 | ||
|
|
039f62209c | ||
|
|
a43ade103c | ||
|
|
a6f1d48670 |
@@ -21,6 +21,7 @@
|
|||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"require-atomic-updates": "off"
|
"require-atomic-updates": "off",
|
||||||
|
"require-await":"warn"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: Question, feedback or bug?
|
name: This channel is CLOSED.
|
||||||
about: Use our community!
|
about: Use our community at https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce
|
||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|||||||
2
.github/workflows/node.js.yml
vendored
2
.github/workflows/node.js.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [10.x, 12.16.2]
|
node-version: [12.x, 14.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -10,8 +10,12 @@ target/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*.log
|
*.log
|
||||||
*.mtar
|
*.mtar
|
||||||
|
*mta-op*/
|
||||||
connection.properties
|
connection.properties
|
||||||
default-env.json
|
default-env.json
|
||||||
packages/messageBox
|
packages/messageBox
|
||||||
reviews/msg-box
|
reviews/msg-box
|
||||||
reviews/db/test.db
|
reviews/db/test.db
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
*log*.txt
|
||||||
@@ -1 +0,0 @@
|
|||||||
parallel: true
|
|
||||||
1
.registry/.gitignore
vendored
Normal file
1
.registry/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.tgz
|
||||||
71
.registry/server.js
Normal file
71
.registry/server.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
const { exec } = require ('child_process')
|
||||||
|
const express = require ('express')
|
||||||
|
const fs = require ('fs')
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
const { PORT=4444 } = process.env
|
||||||
|
const [,,port=PORT] = process.argv
|
||||||
|
process.chdir(__dirname)
|
||||||
|
|
||||||
|
app.use('/-/:tarball', (req,res,next) => {
|
||||||
|
console.debug ('GET', req.params)
|
||||||
|
try {
|
||||||
|
const { tarball } = req.params
|
||||||
|
const [, pkg ] = /^capire-(\w+)/.exec(tarball)
|
||||||
|
fs.lstat(tarball,(err => {
|
||||||
|
if (err) exec(`npm pack ../${pkg}`,next)
|
||||||
|
else next()
|
||||||
|
}))
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
res.sendStatus(500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use('/-', express.static(__dirname))
|
||||||
|
|
||||||
|
app.get('/*', (req,res)=>{
|
||||||
|
const url = decodeURIComponent(req.url)
|
||||||
|
console.debug ('GET',url)
|
||||||
|
try {
|
||||||
|
const [, capire, pkg ] = /^\/(@capire)\/(\w+)/.exec(url)
|
||||||
|
const package = require (`${capire}/${pkg}/package.json`)
|
||||||
|
const tarball = `capire-${pkg}-${package.version}.tgz`
|
||||||
|
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
|
||||||
|
res.json({
|
||||||
|
"name": package.name,
|
||||||
|
"dist-tags": {
|
||||||
|
"latest": package.version
|
||||||
|
},
|
||||||
|
"versions": {
|
||||||
|
[package.version]: {
|
||||||
|
"name": package.name,
|
||||||
|
"version": package.version,
|
||||||
|
"dist": {
|
||||||
|
"tarball": `http://localhost:${port}/-/${tarball}`
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
res.sendStatus(404)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(port, ()=>{
|
||||||
|
console.log (`npm set @capire:registry=http://localhost:${port}`)
|
||||||
|
console.log (`@capire registry listening on http://localhost:${port}`)
|
||||||
|
exec(`npm set @capire:registry=http://localhost:${port}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
const _exit = ()=>{
|
||||||
|
console.log ('\nnpm conf rm @capire:registry')
|
||||||
|
exec('npm conf rm @capire:registry')
|
||||||
|
exec('rm *.tgz')
|
||||||
|
process.exit()
|
||||||
|
}
|
||||||
|
process.on ('SIGTERM',_exit)
|
||||||
|
process.on ('SIGHUP',_exit)
|
||||||
|
process.on ('SIGINT',_exit)
|
||||||
|
process.on ('SIGUSR2',_exit)
|
||||||
29
.reuse/dep5
Normal file
29
.reuse/dep5
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: cloud-cap-samples
|
||||||
|
Upstream-Contact: <Christian Georgi (christian.georgi@sap.com)>
|
||||||
|
Source: https://github.com/SAP-samples/cloud-cap-samples
|
||||||
|
Disclaimer: The code in this project may include calls to APIs (“API Calls”) of
|
||||||
|
SAP or third-party products or services developed outside of this project
|
||||||
|
(“External Products”).
|
||||||
|
“APIs” means application programming interfaces, as well as their respective
|
||||||
|
specifications and implementing code that allows software to communicate with
|
||||||
|
other software.
|
||||||
|
API Calls to External Products are not licensed under the open source license
|
||||||
|
that governs this project. The use of such API Calls and related External
|
||||||
|
Products are subject to applicable additional agreements with the relevant
|
||||||
|
provider of the External Products. In no event shall the open source license
|
||||||
|
that governs this project grant any rights in or to any External Products,or
|
||||||
|
alter, expand or supersede any terms of the applicable additional agreements.
|
||||||
|
If you have a valid license agreement with SAP for the use of a particular SAP
|
||||||
|
External Product, then you may make use of any API Calls included in this
|
||||||
|
project’s code for that SAP External Product, subject to the terms of such
|
||||||
|
license agreement. If you do not have a valid license agreement for the use of
|
||||||
|
a particular SAP External Product, then you may only make use of any API Calls
|
||||||
|
in this project for that SAP External Product for your internal, non-productive
|
||||||
|
and non-commercial test and evaluation of such API Calls. Nothing herein grants
|
||||||
|
you any rights to use or access any SAP External Product, or provide any third
|
||||||
|
parties the right to use of access any SAP External Product, through API Calls.
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2019-2020 SAP SE or an SAP affiliate company and cap-cloud-samples
|
||||||
|
License: Apache-2.0
|
||||||
109
.tours/db-native.tour
Normal file
109
.tours/db-native.tour
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://aka.ms/codetour-schema",
|
||||||
|
"title": "Database Functions",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"title": "Introduction",
|
||||||
|
"description": "### Database Functions in CDS Models\n\nIn this tour, you'll learn how to add database-specific functions to CDS models in your application."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "bookshop/db/schema.cds",
|
||||||
|
"description": "#### Basic Schema\n\nWe want to add two fields to the `Authors` entity, one for the author's age and one for the span of years that she or he lived.\n\nThese two fields can be computed out of the existing `dateOfBirth` and `dateOfDeath` fields.",
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 19,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 21,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Base fields in Author"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "bookshop/srv/admin-service.cds",
|
||||||
|
"description": "This is how the `Authors` entity gets exposed in an OData or REST service.\n\nIn the next step, you'll see how we extend this projection.",
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 4,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Authors service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "fiori/db/sqlite/index.cds",
|
||||||
|
"description": "#### SQLite Implementation\n\nHere's the first implementation for SQLite. It computes the two fields `age` and `lifetime` through SQLite's [strftime](https://sqlite.org/lang_datefunc.html) function.\n\nThrough the [`extend projection`](https://cap.cloud.sap/docs/cds/cdl#extend-view) clause you can add additional fields to projection entities. These are deployed as database views, which is why we can integrate the database functions in the first place.\n",
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 7,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 11,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "SQLite implementation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "fiori/db/hana/index.cds",
|
||||||
|
"description": "#### SAP HANA Implementation\n\nThis is the second implementation for SAP HANA. It computes the same two fields `age` and `lifetime` through the [YEARS_BETWEEN](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/7c0d2c161ea34def86de3f5eadd6a0af.html) and [YEAR](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/20f5fac6751910148dabd3c6821f907d.html) functions of SAP HANA.\n\n#### File Layout and Code Structure\n\nNote the path of the `.cds` file we are in: it's in a subfolder of `db`, so that it's _not_ automatically picked up when we start the application. The same is true for the SQLite implementation: it's in a separate `db/sqlite/` folder as well. In the next step, you'll see how these files are loaded.\n\nAlso, we choose to implement all of that as an extension of the original bookshop here in the _fiori_ package. See the first [CAP Samples] code tour for more details on the different packages of this repository.",
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 7,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 11,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "SAP HANA implementation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "fiori/package.json",
|
||||||
|
"description": "#### Configuration\n\nThe `cds` section in `package.json` is a place to configure which of the `db/sqlite` and `db/hana` folders are used for which database.\nWe use [Node.js profiles](https://cap.cloud.sap/docs/node.js/cds-env#profiles) to separate the configuration.\nIn the `development` profile, you can see that `db/sqlite` is set as the model, while the `db/hana` folder is configured in the `production` profile.",
|
||||||
|
"line": 17,
|
||||||
|
"title": "Configuration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "fiori/package.json",
|
||||||
|
"description": "#### Run with SQLite\n\nTo run with `development` and an in-memory SQLite database, you don't need to do anything special, because it's activated by default. Just run:\n\n>> cds watch fiori\n\nThen open [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) to see the two new fields.\n",
|
||||||
|
"line": 28,
|
||||||
|
"title": "Run with SQLite"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "fiori/package.json",
|
||||||
|
"description": "#### Deploy the CDS Model to SAP HANA\n\nTo 'activate' SAP HANA through the `production` profile, you can use the global `--production` flag:\n\n>> cd fiori; cds deploy --to hana --production\n\n[Learn more about SAP HANA deployment](https://cap.cloud.sap/docs/guides/databases#get-hana)\n\n#### Run the Application\n\n>> cd fiori; cds watch --production\n\nThe service on [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) is the same as before, but this time the `Authors` entity is backed by a database view with an SAP HANA function.\n\n#### More\n\nIf you don't see data, you can add some in the next step.",
|
||||||
|
"line": 31,
|
||||||
|
"title": "Run with SAP HANA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "fiori/test/requests.http",
|
||||||
|
"description": "### Add More Data\n\nOptionally you can add some `Authors` data by clicking on the _Send Request_ link (provided by the [REST client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension).",
|
||||||
|
"line": 68,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 67,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 73,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Add Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Wrap-up",
|
||||||
|
"description": "### Summary\n\nThat's it! You have seen: \n- How to integrate database-specific functions in a CDS model.\n- How to switch between the two implementations for SQLite and SAP HANA."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ref": "master"
|
||||||
|
}
|
||||||
136
.tours/samples.tour
Normal file
136
.tours/samples.tour
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://aka.ms/codetour-schema",
|
||||||
|
"title": "CAP Samples",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"title": "Welcome",
|
||||||
|
"file": "README.md",
|
||||||
|
"description": "### Welcome to CAP Samples!\n\nThis tour leads you through a collection of samples for the [SAP Cloud Application Programming Model (CAP)](https://cap.cloud.sap).\nYou will learn which features of the programming model are demonstrated in which sample.\n\nLet's start!",
|
||||||
|
"line": 2,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 3,
|
||||||
|
"character": 108
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "hello/world.cds",
|
||||||
|
"description": "### Hello World!\n\nThis is a simplistic [Hello World](https://cap.cloud.sap/docs/get-started/hello-world) service using [CDS](https://cap.cloud.sap/docs/cds/) and [cds.services](https://cap.cloud.sap/docs/node.js/api#services-api).",
|
||||||
|
"line": 2,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 4,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Hello World!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "bookshop/db/schema.cds",
|
||||||
|
"description": "### A Bookshop!\n\nIntroduces:\n- [Project Setup](https://cap.cloud.sap/docs/get-started/) and [Layouts](https://cap.cloud.sap/docs/get-started/projects)\n- [Domain Modeling](https://cap.cloud.sap/docs/guides/domain-models)\n- [Defining Services](https://cap.cloud.sap/docs/guides/providing-services)\n- [Generic Providers](https://cap.cloud.sap/docs/guides/generic-providers)\n- [Adding Custom Logic](https://cap.cloud.sap/docs/guides/service-impl)\n- [Using Databases](https://cap.cloud.sap/docs/guides/databases)\n",
|
||||||
|
"line": 1,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 32,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Bookshop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "common/index.cds",
|
||||||
|
"description": "### Extend and Reuse\n\nShowcases how to extend [@sap/cds/common](https://cap.cloud.sap/docs/cds/common) thereby covering:\n- Building [extension packages](https://cap.cloud.sap/docs/guides/domain-models#aspects-extensibility)\n- Providing [reuse packages](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content)\n- [Verticalization](https://cap.cloud.sap/docs/cds/common#adapting-to-your-needs)\n- Using [Aspects](https://cap.cloud.sap/docs/cds/cdl#aspects)\n- Used in the [fiori app sample](#fiori)\n",
|
||||||
|
"line": 1,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 46,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Common"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "orders/db/schema.cds",
|
||||||
|
"description": "### Compositions and Serving Documents\n\nA standalone orders management service, demonstrating:\n- Using [Compositions](https://cap.cloud.sap/docs/cds/cdl#compositions) in [Domain Models](https://cap.cloud.sap/docs/guides/domain-models), along with\n- [Serving deeply nested documents](https://cap.cloud.sap/docs/guides/generic-providers#serving-structured-data)\n",
|
||||||
|
"line": 1,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 27,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Orders"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "reviews/db/schema.cds",
|
||||||
|
"description": "### More Modularity\n\nShows how to implement a modular service to manage product reviews, including:\n- Consuming other services synchronously and asynchronously\n- Serving requests synchronously\n- Emitting events asynchronously\n- Grow as you go, with:\n- Mocking app services\n- Running service meshes\n- Late-cut Micro Services\n- As well as managed data, input validations, and authorization\n",
|
||||||
|
"line": 1,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 39,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Reviews"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "fiori/app/index.cds",
|
||||||
|
"description": "### Annotations for SAP Fiori Elements\n\nA [composite app, reusing and combining](https://cap.cloud.sap/docs/guides/verticalize) these packages:\n - [@capire/bookshop](bookshop)\n - [@capire/reviews](reviews)\n - [@capire/orders](orders)\n - [@capire/common](common)\n\n[Adds a SAP Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookshop, thereby introducing to:\n - [OData Annotations](https://cap.cloud.sap/docs/guides/fiori#adding-odata-annotations) in `.cds` files\n - Support for [Fiori Draft](https://cap.cloud.sap/docs/guides/fiori#draft)\n - Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help)\n - Serving SAP Fiori apps locally\n\n[The Vue.js app](bookshop/app/vue) imported from bookshop is served as well.\n",
|
||||||
|
"line": 1,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 13,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Fiori"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "package.json",
|
||||||
|
"description": "### All-in-one Monorepo\n\nEach sample sub directory essentially is a standard npm package, some with standard npm dependencies to other samples. The root folder's [package.json](package.json) has local links to the sub folders, such that an `npm install` populates a local `node_modules` folder acts like a local npm registry to the individual sample packages.\n",
|
||||||
|
"line": 8,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 8,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 15,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Packages"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isPrimary": true,
|
||||||
|
"description": "Overview of CAP Samples for Node.js"
|
||||||
|
}
|
||||||
5
.vscode/extensions.json
vendored
5
.vscode/extensions.json
vendored
@@ -4,14 +4,15 @@
|
|||||||
|
|
||||||
// List of extensions which should be recommended for users of this workspace.
|
// List of extensions which should be recommended for users of this workspace.
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"SAPSE.vscode-cds",
|
"sapse.vscode-cds",
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"mechatroner.rainbow-csv",
|
"mechatroner.rainbow-csv",
|
||||||
"humao.rest-client",
|
"humao.rest-client",
|
||||||
"alexcvzz.vscode-sqlite",
|
"alexcvzz.vscode-sqlite",
|
||||||
"hbenl.vscode-mocha-test-adapter",
|
"hbenl.vscode-mocha-test-adapter",
|
||||||
"sdras.night-owl"
|
"sdras.night-owl",
|
||||||
|
"vsls-contrib.codetour"
|
||||||
],
|
],
|
||||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||||
"unwantedRecommendations": [
|
"unwantedRecommendations": [
|
||||||
|
|||||||
28
.vscode/launch.json
vendored
28
.vscode/launch.json
vendored
@@ -6,17 +6,29 @@
|
|||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "bookshop",
|
"name": "bookshop",
|
||||||
"command": "cds watch bookshop",
|
"command": "npx cds watch bookshop",
|
||||||
"request": "launch",
|
|
||||||
"type": "node-terminal",
|
"type": "node-terminal",
|
||||||
"skipFiles": ["<node_internals>/**"]
|
"request": "launch",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/cds/lib/lazy.js",
|
||||||
|
"**/cds/lib/req/cls.js",
|
||||||
|
"**/odata-v4/okra/**"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Fiori app",
|
"name": "Fiori App",
|
||||||
"command": "cds watch fiori",
|
"command": "npx cds watch fiori",
|
||||||
"request": "launch",
|
|
||||||
"type": "node-terminal",
|
"type": "node-terminal",
|
||||||
"skipFiles": ["<node_internals>/**"]
|
"request": "launch",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/cds/lib/lazy.js",
|
||||||
|
"**/cds/lib/req/cls.js",
|
||||||
|
"**/odata-v4/okra/**"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"inputs": [
|
"inputs": [
|
||||||
@@ -24,7 +36,7 @@
|
|||||||
"type": "pickString",
|
"type": "pickString",
|
||||||
"id": "sample",
|
"id": "sample",
|
||||||
"description": "Which sample do you want to start?",
|
"description": "Which sample do you want to start?",
|
||||||
"options": ["bookshop", "fiori", "reviews", "reviews/test/bookshop"],
|
"options": [ "bookshop", "fiori", "reviews", "reviews" ],
|
||||||
"default": "bookshop"
|
"default": "bookshop"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
15
.vscode/settings.json
vendored
15
.vscode/settings.json
vendored
@@ -1,6 +1,17 @@
|
|||||||
{
|
{
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
|
".reuse/**": true,
|
||||||
"**/.gitignore": true,
|
"**/.gitignore": true,
|
||||||
"**/.vscode": true
|
"**/.vscode": true,
|
||||||
}
|
"LICENSES/**": true
|
||||||
|
},
|
||||||
|
"debug.javascript.terminalOptions": {
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/cds/lib/lazy.js",
|
||||||
|
"**/cds/lib/req/cls.js",
|
||||||
|
"**/odata-v4/okra/**"
|
||||||
|
]
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,19 +199,3 @@
|
|||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
--------------------------------------------------------------------------------------------------------------------------
|
|
||||||
APIs
|
|
||||||
|
|
||||||
This project may include APIs to SAP or third-party products or services.
|
|
||||||
The use of these APIs, products and services may be subject to additional
|
|
||||||
agreements. In no event shall the application of the Apache Software
|
|
||||||
License, v.2 to this project grant any rights in or to these APIs,
|
|
||||||
products or services that would alter, expand, be inconsistent with,
|
|
||||||
or supersede any terms of these additional agreements. “API” means
|
|
||||||
application programming interfaces, as well as their respective
|
|
||||||
specifications and implementing code that allows other software
|
|
||||||
products to communicate with or call on SAP or third party products
|
|
||||||
or services (for example, SAP Enterprise Services, BAPIs, Idocs, RFCs
|
|
||||||
and ABAP calls or other user exits) and may be made available through
|
|
||||||
SAP or third party products, SDKs, documentation or other media.
|
|
||||||
40
README.md
40
README.md
@@ -3,17 +3,21 @@
|
|||||||
Find here a collection of samples for the [SAP Cloud Application Programming Model](https://cap.cloud.sap) organized in a simplistic [monorepo setup](samples.md#all-in-one-monorepo). → See [**Overview** of contained samples](samples.md)
|
Find here a collection of samples for the [SAP Cloud Application Programming Model](https://cap.cloud.sap) organized in a simplistic [monorepo setup](samples.md#all-in-one-monorepo). → See [**Overview** of contained samples](samples.md)
|
||||||
|
|
||||||

|

|
||||||
|
[](https://api.reuse.software/info/github.com/SAP-samples/cloud-cap-samples)
|
||||||
|
|
||||||
### Preliminaries
|
### Preliminaries
|
||||||
|
|
||||||
1. [Install @sap/cds-dk](https://cap.cloud.sap/docs/get-started/) as documented in [capire](https://cap.cloud.sap)
|
1. Install [**@sap/cds-dk**](https://cap.cloud.sap/docs/get-started/) globally:
|
||||||
2. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/in-vscode)
|
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i -g @sap/cds-dk
|
||||||
|
```
|
||||||
|
|
||||||
|
2. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/tools#vscode)
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
Clone this repo as shown below, if you have [git](https://git-scm.com/downloads) installed,
|
If you've [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/master.zip).
|
||||||
otherwise [download as zip file](archive/master.zip).
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/sap-samples/cloud-cap-samples samples
|
git clone https://github.com/sap-samples/cloud-cap-samples samples
|
||||||
@@ -38,21 +42,45 @@ cds watch bookshop
|
|||||||
|
|
||||||
After that open this link in your browser: [http://localhost:4004](http://localhost:4004)
|
After that open this link in your browser: [http://localhost:4004](http://localhost:4004)
|
||||||
|
|
||||||
|
When asked to log in, type `alice` as user and leave the password field blank, which is the [default user](https://cap.cloud.sap/docs/node.js/authentication#mocked).
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
Run the provided tests with [_jest_](http://jestjs.io) or [_mocha_](http://mochajs.org), for example:
|
Run the provided tests with [_jest_](http://jestjs.io) or [_mocha_](http://mochajs.org), for example:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npx jest
|
npx jest
|
||||||
```
|
```
|
||||||
> While mocha is a bit smaller and faster, jest runs tests in parallel and isolation, which allows to run all tests.
|
> While mocha is a bit smaller and faster, jest runs tests in parallel and isolation, which allows to run all tests.
|
||||||
|
|
||||||
|
|
||||||
|
### Serve `npm`
|
||||||
|
|
||||||
|
We've included a simple npm registry mock, which allows you to do an `npm install @capire/<package>` locally. Use it as follows:
|
||||||
|
|
||||||
|
1. Start the @capire registry:
|
||||||
|
```sh
|
||||||
|
npm run registry
|
||||||
|
```
|
||||||
|
> While running this will have `@capire:registry=http://localhost:4444` set with npmrc.
|
||||||
|
|
||||||
|
2. Install one of the @capire packages wherever you like, for example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm add @capire/common @capire/bookshop
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Code Tours
|
||||||
|
|
||||||
|
Take one of the [guided tours](.tours) in VS Code through our CAP samples and learn which CAP features are showcased by the different parts of the repository. Just install the [CodeTour extension](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour) for VS Code. We'll add more code tours in the future. Stay tuned!
|
||||||
|
|
||||||
## Get Support
|
## Get Support
|
||||||
|
|
||||||
Check out the documentation at [https://cap.cloud.sap](https://cap.cloud.sap). <br>
|
Check out the documentation at [https://cap.cloud.sap](https://cap.cloud.sap). <br>
|
||||||
In case you have a question, find a bug, or otherwise need support, please use our [community](https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce).
|
In case you've a question, find a bug, or otherwise need support, use our [community](https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce) to get more visibility.
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSE) file.
|
Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE.txt) file.
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
cds.features.snapi = y
|
|
||||||
@@ -9,33 +9,36 @@ const books = new Vue ({
|
|||||||
|
|
||||||
data: {
|
data: {
|
||||||
list: [],
|
list: [],
|
||||||
book: { descr:'( click on a row to see details... )' },
|
book: undefined,
|
||||||
order: { amount:1, succeeded:'', failed:'' }
|
order: { amount:1, succeeded:'', failed:'' }
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
search: ({target:{value:v}}) => books.fetch (v && '$search='+v),
|
search: ({target:{value:v}}) => books.fetch(v && '&$search='+v),
|
||||||
|
|
||||||
async fetch (_filter='') {
|
async fetch (etc='') {
|
||||||
const columns = 'ID,title,author,price,stock', details = 'genre,currency'
|
const {data} = await GET(`/ListOfBooks?$expand=genre,currency${etc}`)
|
||||||
const {data} = await GET(`/Books?$select=${columns}&$expand=${details}&${_filter}`)
|
|
||||||
books.list = data.value
|
books.list = data.value
|
||||||
},
|
},
|
||||||
|
|
||||||
async inspect () {
|
async inspect (eve) {
|
||||||
const book = books.book = books.list [event.currentTarget.rowIndex-1]
|
const book = books.book = books.list [eve.currentTarget.rowIndex-1]
|
||||||
book.descr || await GET(`/Books/${book.ID}/descr/$value`) .then (({data}) => book.descr = data)
|
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`)
|
||||||
|
Object.assign (book, res.data)
|
||||||
books.order = { amount:1 }
|
books.order = { amount:1 }
|
||||||
setTimeout (()=> $('form > input').focus(), 111)
|
setTimeout (()=> $('form > input').focus(), 111)
|
||||||
},
|
},
|
||||||
|
|
||||||
submitOrder () { event.preventDefault()
|
async submitOrder () {
|
||||||
const {book,order} = books, amount = parseInt (order.amount) || 1
|
const {book,order} = books, amount = parseInt (order.amount) || 1 // REVISIT: Okra should be less strict
|
||||||
POST(`/submitOrder`, { amount, book: book.ID })
|
try {
|
||||||
.then (()=> books.order = { amount, succeeded: `Successfully orderd ${amount} item(s).` })
|
const res = await POST(`/submitOrder`, { amount, book: book.ID })
|
||||||
.catch (e=> books.order = { amount, failed: e.response.data.error.message })
|
book.stock = res.data.stock
|
||||||
GET(`/Books/${book.ID}/stock/$value`).then (res => book.stock = res.data)
|
books.order = { amount, succeeded: `Successfully orderd ${amount} item(s).` }
|
||||||
|
} catch (e) {
|
||||||
|
books.order = { amount, failed: e.response.data.error.message }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,50 +7,56 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
|
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
|
||||||
<style>
|
<style>
|
||||||
#books tr:hover { background: #f2f2f2; cursor: pointer; }
|
.hovering tr:hover td { color:cyan; background: #123; cursor: pointer; }
|
||||||
form { float:right; display:flex; flex-direction: row-reverse }
|
.rating-stars { color:teal }
|
||||||
form #amount { width: 5em }
|
.succeeded { color:teal }
|
||||||
.is-success { color: #0d920d }
|
.failed { color:red }
|
||||||
.has-error { color: #df1010 }
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="small-container", style="margin-top: 70px;">
|
<body class="small-container", style="margin-top: 70px;">
|
||||||
<div id='app'>
|
<div id='app'>
|
||||||
|
|
||||||
<h1> Capire Books </h1>
|
<h1> {{ document.title }} </h1>
|
||||||
|
|
||||||
<input type="text" placeholder="Search..." @input="search">
|
<input type="text" placeholder="Search..." @input="search">
|
||||||
|
|
||||||
<table id='books'>
|
<table id='books' class="hovering">
|
||||||
<thead>
|
<thead>
|
||||||
<th> Book </th>
|
<th> Book </th>
|
||||||
<th> Author </th>
|
<th> Author </th>
|
||||||
<th> Genre </th>
|
<th> Genre </th>
|
||||||
|
<th> Rating </th>
|
||||||
<th> Price </th>
|
<th> Price </th>
|
||||||
</thead>
|
</thead>
|
||||||
<tr v-for="book in list" v-bind:id="book.ID" v-on:click="inspect">
|
<tr v-for="book in list" v-bind:id="book.ID" v-on:click="inspect">
|
||||||
<td>{{ book.title }}</td>
|
<td>{{ book.title }}</td>
|
||||||
<td>{{ book.author }}</td>
|
<td>{{ book.author }}</td>
|
||||||
<td>{{ book.genre.name }}</td>
|
<td>{{ book.genre.name }}</td>
|
||||||
<td>{{ book.currency.symbol }} {{ book.price }}</td>
|
<td class="rating-stars">
|
||||||
|
{{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }}
|
||||||
|
</td>
|
||||||
|
<td>{{ book.currency && book.currency.symbol }} {{ book.price }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div v-if="book.title">
|
<div v-if="book">
|
||||||
|
<img v-bind:src="book.image" alt=""/>
|
||||||
<label style="text-align:right">
|
<label style="text-align:right">
|
||||||
<span class="is-success"> {{ order.succeeded }} </span>
|
<span class="succeeded"> {{ order.succeeded }} </span>
|
||||||
<span class="has-error"> {{ order.failed }} </span>
|
<span class="failed"> {{ order.failed }} </span>
|
||||||
{{ book.stock }} in stock
|
{{ book.stock }} in stock
|
||||||
</label>
|
</label>
|
||||||
<form @submit="submitOrder">
|
<form @submit.prevent="submitOrder" style="float:right; display:flex; flex-direction:row-reverse">
|
||||||
<input type="number" id="amount" v-model="order.amount" v-bind:class="{ 'has-error': order.failed }">
|
<input type="number" v-model="order.amount" v-bind:class="{ failed: order.failed }" style="width:5em">
|
||||||
<input type="submit" value="Order:" class="muted-button">
|
<input type="submit" value="Order:" class="muted-button">
|
||||||
</form>
|
</form>
|
||||||
|
<h4> {{ book.title }} </h4>
|
||||||
|
<p> {{ book.descr }} </p>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
( click on a row to see details... )
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4> {{ book.title }} </h4>
|
|
||||||
<p> {{ book.descr }} </p>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ entity Books : managed {
|
|||||||
author : Association to Authors;
|
author : Association to Authors;
|
||||||
genre : Association to Genres;
|
genre : Association to Genres;
|
||||||
stock : Integer;
|
stock : Integer;
|
||||||
price : Decimal(9,2);
|
price : Decimal;
|
||||||
currency : Currency;
|
currency : Currency;
|
||||||
|
image : LargeBinary @Core.MediaType : 'image/png';
|
||||||
}
|
}
|
||||||
|
|
||||||
entity Authors : managed {
|
entity Authors : managed {
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A simple self-contained bookshop service.",
|
"description": "A simple self-contained bookshop service.",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capire/common": "../common",
|
"@capire/common": "*",
|
||||||
"@sap/cds": ">=3.33.1",
|
"@sap/cds": "^5.0.4",
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1",
|
||||||
|
"passport": "0.4.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"genres": "cds serve test/genres.cds",
|
"genres": "cds serve test/genres.cds",
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ This stand-alone sample introduces the essential tasks in the development of CAP
|
|||||||
## Hypothetical Use Cases
|
## Hypothetical Use Cases
|
||||||
|
|
||||||
1. Build a service that allows to browse _Books_ and _Authors_.
|
1. Build a service that allows to browse _Books_ and _Authors_.
|
||||||
2. Books have assigned _Genres_ which are organized hierarchically.
|
2. Books have assigned _Genres_, which are organized hierarchically.
|
||||||
3. All users may browse books without login.
|
3. All users may browse books without login.
|
||||||
4. All entries are maintained by Administrators.
|
4. All entries are maintained by Administrators.
|
||||||
5. End users may order books (the actual order mgmt being out of scope)
|
5. End users may order books (the actual order mgmt being out of scope).
|
||||||
|
|
||||||
## Running the Sample
|
## Running the Sample
|
||||||
|
|
||||||
@@ -20,12 +20,12 @@ npm run watch
|
|||||||
|
|
||||||
| Links to capire | Sample files / folders |
|
| Links to capire | Sample files / folders |
|
||||||
| --------------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
| --------------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
||||||
| [Project Setup and Layouts](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content) | [`./`](./) |
|
| [Project Setup & Layouts](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content) | [`./`](./) |
|
||||||
| [Defining Domain Models](https://cap.cloud.sap/docs/guides/domain-models) | [`./db/schema.cds`](./db/schema.cds) |
|
| [Domain Modeling with CDS](https://cap.cloud.sap/docs/guides/domain-models) | [`./db/schema.cds`](./db/schema.cds) |
|
||||||
| [Defining Services](https://cap.cloud.sap/docs/guides/providing-services) | [`./srv/*.cds`](./srv) |
|
| [Defining Services](https://cap.cloud.sap/docs/guides/services#defining-services) | [`./srv/*.cds`](./srv) |
|
||||||
| [Single-purposed Services](https://cap.cloud.sap/docs/guides/providing-services#single-purposed-services) | [`./srv/*.cds`](./srv) |
|
| [Single-purposed Services](https://cap.cloud.sap/docs/guides/services#single-purposed-services) | [`./srv/*.cds`](./srv) |
|
||||||
| [Generic Providers](https://cap.cloud.sap/docs/guides/providing-services) | http://localhost:4004 |
|
| [Providing & Consuming Providers](https://cap.cloud.sap/docs/guides/providing-services) | http://localhost:4004 |
|
||||||
| Using Databases | [`./db/data/*.csv`](./db/data) |
|
| [Using Databases](https://cap.cloud.sap/docs/guides/databases) | [`./db/data/*.csv`](./db/data) |
|
||||||
| [Adding Custom Logic](https://cap.cloud.sap/docs/guides/service-impl) | [`./srv/*.js`](./srv) |
|
| [Adding Custom Logic](https://cap.cloud.sap/docs/guides/service-impl) | [`./srv/*.js`](./srv) |
|
||||||
| Adding Tests | [`./test`](./test) |
|
| Adding Tests | [`./test`](./test) |
|
||||||
| [Sharing for Reuse](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content) | [`./index.cds`](./index.cds) |
|
| [Sharing for Reuse](https://cap.cloud.sap/docs/guides/reuse-and-compose) | [`./index.cds`](./index.cds) |
|
||||||
|
|||||||
@@ -1,5 +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;
|
||||||
entity Authors as projection on my.Authors;
|
entity Authors as projection on my.Authors;
|
||||||
}
|
}
|
||||||
|
|||||||
12
bookshop/srv/admin-service.js
Normal file
12
bookshop/srv/admin-service.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const cds = require('@sap/cds')
|
||||||
|
|
||||||
|
module.exports = cds.service.impl (function(){
|
||||||
|
this.before ('NEW','Authors', genid)
|
||||||
|
this.before ('NEW','Books', genid)
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Generate primary keys for target entity in request */
|
||||||
|
async function genid (req) {
|
||||||
|
const {ID} = await cds.tx(req).run (SELECT.one.from(req.target).columns('max(ID) as ID'))
|
||||||
|
req.data.ID = ID - ID % 100 + 100 + 1
|
||||||
|
}
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
using { sap.capire.bookshop as my } from '../db/schema';
|
using { sap.capire.bookshop as my } from '../db/schema';
|
||||||
service CatalogService @(path:'/browse') {
|
service CatalogService @(path:'/browse') {
|
||||||
|
|
||||||
@readonly entity Books as SELECT from my.Books {*,
|
/** For displaying lists of Books */
|
||||||
|
@readonly entity ListOfBooks as projection on Books
|
||||||
|
excluding { descr };
|
||||||
|
|
||||||
|
/** For display in details pages */
|
||||||
|
@readonly entity Books as projection on my.Books { *,
|
||||||
author.name as author
|
author.name as author
|
||||||
} excluding { createdBy, modifiedBy };
|
} excluding { createdBy, modifiedBy };
|
||||||
|
|
||||||
@requires_: 'authenticated-user'
|
@requires: 'authenticated-user'
|
||||||
action submitOrder (book : Books.ID, amount: Integer);
|
action submitOrder ( book: Books:ID, amount: Integer ) returns { stock: Integer };
|
||||||
|
event OrderedBook : { book: Books:ID; amount: Integer; buyer: String };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,26 @@
|
|||||||
const cds = require('@sap/cds')
|
const cds = require('@sap/cds')
|
||||||
module.exports = async function (){
|
const { Books } = cds.entities ('sap.capire.bookshop')
|
||||||
|
|
||||||
const db = await cds.connect.to('db') // connect to database service
|
class CatalogService extends cds.ApplicationService { init(){
|
||||||
const { Books } = db.entities // get reflected definitions
|
|
||||||
|
|
||||||
// Reduce stock of ordered books if available stock suffices
|
// Reduce stock of ordered books if available stock suffices
|
||||||
this.on ('submitOrder', async req => {
|
this.on ('submitOrder', async req => {
|
||||||
const {book,amount} = req.data
|
const {book,amount} = req.data
|
||||||
const n = await UPDATE (Books, book)
|
let {stock} = await SELECT `stock` .from (Books,book)
|
||||||
.with ({ stock: {'-=': amount }})
|
if (stock >= amount) {
|
||||||
.where ({ stock: {'>=': amount }})
|
await UPDATE (Books,book) .with (`stock -=`, amount)
|
||||||
n > 0 || req.error (409,`${amount} exceeds stock for book #${book}`)
|
await this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
|
||||||
|
return { stock }
|
||||||
|
}
|
||||||
|
else return req.error (409,`${amount} exceeds stock for book #${book}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add some discount for overstocked books
|
// Add some discount for overstocked books
|
||||||
this.after ('READ','Books', each => {
|
this.after ('READ','ListOfBooks', each => {
|
||||||
if (each.stock > 111) each.title += ` -- 11% discount!`
|
if (each.stock > 111) each.title += ` -- 11% discount!`
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
return super.init()
|
||||||
|
}}
|
||||||
|
|
||||||
|
module.exports = { CatalogService }
|
||||||
|
|||||||
@@ -32,6 +32,37 @@ GET {{server}}/admin/Authors?
|
|||||||
# &sap-language=de
|
# &sap-language=de
|
||||||
Authorization: Basic alice:
|
Authorization: Basic alice:
|
||||||
|
|
||||||
|
### ------------------------------------------------------------------------
|
||||||
|
# Create book
|
||||||
|
POST {{server}}/admin/Books
|
||||||
|
Content-Type: application/json;IEEE754Compatible=true
|
||||||
|
Authorization: Basic alice:
|
||||||
|
|
||||||
|
{
|
||||||
|
"ID": 2,
|
||||||
|
"title": "Poems : Pocket Poets",
|
||||||
|
"descr": "The Everyman's Library Pocket Poets hardcover series is popular for its compact size and reasonable price which does not compromise content. Poems: Bronte contains poems that demonstrate a sensibility elemental in its force with an imaginative discipline and flexibility of the highest order. Also included are an Editor's Note and an index of first lines.",
|
||||||
|
"author": { "ID": 101 },
|
||||||
|
"genre": { "ID": 12 },
|
||||||
|
"stock": 5,
|
||||||
|
"price": "12.05",
|
||||||
|
"currency": { "code": "USD" }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### ------------------------------------------------------------------------
|
||||||
|
# Put image to books
|
||||||
|
PUT {{server}}/admin/Books(2)/image
|
||||||
|
Content-Type: image/png
|
||||||
|
Authorization: Basic alice:
|
||||||
|
|
||||||
|
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANwAAADcCAYAAAAbWs+BAAAGwElEQVR4Ae3cwZFbNxBFUY5rkrDTmKAUk5QT03Aa44U22KC7NHptw+DRikVAXf8fzC3u8Hj4R4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgZzAW26USQT+e4HPx+Mz+RRvj0e0kT+SD2cWAQK1gOBqH6sEogKCi3IaRqAWEFztY5VAVEBwUU7DCNQCgqt9rBKICgguymkYgVpAcLWPVQJRAcFFOQ0jUAsIrvaxSiAqILgop2EEagHB1T5WCUQFBBflNIxALSC42scqgaiA4KKchhGoBQRX+1glEBUQXJTTMAK1gOBqH6sEogKCi3IaRqAWeK+Xb1z9iN558fHxcSPS9p2ezx/ROz4e4TtIHt+3j/61hW9f+2+7/+UXbifjewIDAoIbQDWSwE5AcDsZ3xMYEBDcAKqRBHYCgtvJ+J7AgIDgBlCNJLATENxOxvcEBgQEN4BqJIGdgOB2Mr4nMCAguAFUIwnsBAS3k/E9gQEBwQ2gGklgJyC4nYzvCQwICG4A1UgCOwHB7WR8T2BAQHADqEYS2AkIbifjewIDAoIbQDWSwE5AcDsZ3xMYEEjfTzHwiK91B8npd6Q8n8/oGQ/ckRJ9vvQwv3BpUfMIFAKCK3AsEUgLCC4tah6BQkBwBY4lAmkBwaVFzSNQCAiuwLFEIC0guLSoeQQKAcEVOJYIpAUElxY1j0AhILgCxxKBtIDg0qLmESgEBFfgWCKQFhBcWtQ8AoWA4AocSwTSAoJLi5pHoBAQXIFjiUBaQHBpUfMIFAKCK3AsEUgLCC4tah6BQmDgTpPsHSTFs39p6fQ7Q770UsV/Ov19X+2OFL9wxR+rJQJpAcGlRc0jUAgIrsCxRCAtILi0qHkECgHBFTiWCKQFBJcWNY9AISC4AscSgbSA4NKi5hEoBARX4FgikBYQXFrUPAKFgOAKHEsE0gKCS4uaR6AQEFyBY4lAWkBwaVHzCBQCgitwLBFICwguLWoegUJAcAWOJQJpAcGlRc0jUAgIrsCxRCAt8J4eePq89B0ar3ZnyOnve/rfn1+400/I810lILirjtPLnC4guNNPyPNdJSC4q47Ty5wuILjTT8jzXSUguKuO08ucLiC400/I810lILirjtPLnC4guNNPyPNdJSC4q47Ty5wuILjTT8jzXSUguKuO08ucLiC400/I810lILirjtPLnC4guNNPyPNdJSC4q47Ty5wuILjTT8jzXSUguKuO08ucLiC400/I810l8JZ/m78+szP/zI47fJo7Q37vgJ7PHwN/07/3TOv/9gu3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhg4P6H9J0maYHXuiMlrXf+vOfA33Turf3C5SxNItAKCK4lsoFATkBwOUuTCLQCgmuJbCCQExBcztIkAq2A4FoiGwjkBASXszSJQCsguJbIBgI5AcHlLE0i0AoIriWygUBOQHA5S5MItAKCa4lsIJATEFzO0iQCrYDgWiIbCOQEBJezNIlAKyC4lsgGAjkBweUsTSLQCgiuJbKBQE5AcDlLkwi0Akff//Dz6U+/I6U1/sUNr3bnytl3kPzi4bXb/cK1RDYQyAkILmdpEoFWQHAtkQ0EcgKCy1maRKAVEFxLZAOBnIDgcpYmEWgFBNcS2UAgJyC4nKVJBFoBwbVENhDICQguZ2kSgVZAcC2RDQRyAoLLWZpEoBUQXEtkA4GcgOByliYRaAUE1xLZQCAnILicpUkEWgHBtUQ2EMgJCC5naRKBVkBwLZENBHIC/4M7TXIv+3PS22d24qvdQfL3C/7N5P5i/MLlLE0i0AoIriWygUBOQHA5S5MItAKCa4lsIJATEFzO0iQCrYDgWiIbCOQEBJezNIlAKyC4lsgGAjkBweUsTSLQCgiuJbKBQE5AcDlLkwi0AoJriWwgkBMQXM7SJAKtgOBaIhsI5AQEl7M0iUArILiWyAYCOQHB5SxNItAKCK4lsoFATkBwOUuTCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAvyrwDySEJ2VQgUSoAAAAAElFTkSuQmCC
|
||||||
|
|
||||||
|
|
||||||
|
### ------------------------------------------------------------------------
|
||||||
|
# Reading image from from the server directly
|
||||||
|
GET {{server}}/browse/Books(2)/image
|
||||||
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
### ------------------------------------------------------------------------
|
||||||
# Submit Order as authenticated user
|
# Submit Order as authenticated user
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@capire/common",
|
"name": "@capire/common",
|
||||||
"version": "1.0.0"
|
"description": "Provides a pre-built extension package for std @sap/cds/common",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@sap/cds": "*"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
cds.features.snapi = y
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// Proxy for importing schema from bookshop sample
|
|
||||||
using from '@capire/bookshop';
|
|
||||||
namespace sap.capire.bookshop;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@capire/fiori",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@capire/bookshop": "../bookshop",
|
|
||||||
"@capire/orders": "../orders",
|
|
||||||
"@capire/common": "../common",
|
|
||||||
"@sap/cds": ">=3.33.1",
|
|
||||||
"express": "^4.17.1"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "cds run --in-memory?",
|
|
||||||
"watch": "cds watch"
|
|
||||||
},
|
|
||||||
"cds": {
|
|
||||||
"requires": {
|
|
||||||
"db": {
|
|
||||||
"kind": "sql"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// Proxy for importing services from bookshop sample
|
|
||||||
using from '@capire/bookshop';
|
|
||||||
annotate AdminService with @impl:'srv/admin-service.js';
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
const cds = require('@sap/cds')
|
|
||||||
|
|
||||||
module.exports = cds.service.impl (async function() {
|
|
||||||
const {Books} = cds.entities
|
|
||||||
const {ID} = await SELECT.one.from(Books).columns('max(ID) as ID')
|
|
||||||
let newID = ID - ID % 100 + 100
|
|
||||||
this.before ('NEW','Books', req => req.data.ID = ++newID)
|
|
||||||
})
|
|
||||||
BIN
media/test/Test.png
Normal file
BIN
media/test/Test.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
35
media/test/media.http
Normal file
35
media/test/media.http
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
### 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:
|
||||||
1
multitenancy/.cdsrc.json
Normal file
1
multitenancy/.cdsrc.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
33
multitenancy/.gitignore
vendored
Normal file
33
multitenancy/.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# CAP multitenancy
|
||||||
|
_out
|
||||||
|
*.db
|
||||||
|
connection.properties
|
||||||
|
default-*.json
|
||||||
|
gen/
|
||||||
|
node_modules/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Web IDE, App Studio
|
||||||
|
.che/
|
||||||
|
.gen/
|
||||||
|
|
||||||
|
# MTA
|
||||||
|
*_mta_build_tmp
|
||||||
|
*.mtar
|
||||||
|
*.mta
|
||||||
|
mta_archives/
|
||||||
|
*mta-op*/
|
||||||
|
|
||||||
|
|
||||||
|
# Other
|
||||||
|
.DS_Store
|
||||||
|
*.orig
|
||||||
|
*.log
|
||||||
|
|
||||||
|
*.iml
|
||||||
|
*.flattened-pom.xml
|
||||||
|
*log*.txt
|
||||||
|
|
||||||
|
# IDEs
|
||||||
|
# .vscode
|
||||||
|
# .idea
|
||||||
20
multitenancy/.vscode/extensions.json
vendored
Normal file
20
multitenancy/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||||
|
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||||
|
|
||||||
|
// List of extensions which should be recommended for users of this workspace.
|
||||||
|
"recommendations": [
|
||||||
|
// >>>>>>>> Add CDS Editor here as soon it is available of vscode marketplace!,
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"mechatroner.rainbow-csv",
|
||||||
|
"humao.rest-client",
|
||||||
|
"alexcvzz.vscode-sqlite",
|
||||||
|
"hbenl.vscode-mocha-test-adapter",
|
||||||
|
"sdras.night-owl"
|
||||||
|
],
|
||||||
|
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
15
multitenancy/.vscode/launch.json
vendored
Normal file
15
multitenancy/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"command": "cds run --with-mocks --in-memory?",
|
||||||
|
"name": "cds run",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "node-terminal",
|
||||||
|
"skipFiles": [ "<node_internals>/**" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7
multitenancy/.vscode/settings.json
vendored
Normal file
7
multitenancy/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.gitignore": true,
|
||||||
|
"**/.git": true,
|
||||||
|
"**/.vscode": true
|
||||||
|
}
|
||||||
|
}
|
||||||
25
multitenancy/.vscode/tasks.json
vendored
Normal file
25
multitenancy/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "shell",
|
||||||
|
"label": "cds watch",
|
||||||
|
"command": "cds",
|
||||||
|
"args": ["watch"],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "shell",
|
||||||
|
"label": "cds run",
|
||||||
|
"command": "cds",
|
||||||
|
"args": ["run", "--with-mocks", "--in-memory?"],
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
multitenancy/README.md
Normal file
25
multitenancy/README.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Getting Started
|
||||||
|
|
||||||
|
Welcome to your new project.
|
||||||
|
|
||||||
|
It contains these folders and files, following our recommended project layout:
|
||||||
|
|
||||||
|
File or Folder | Purpose
|
||||||
|
---------|----------
|
||||||
|
`app/` | content for UI frontends goes here
|
||||||
|
`db/` | your domain models and data go here
|
||||||
|
`srv/` | your service models and code go here
|
||||||
|
`package.json` | project metadata and configuration
|
||||||
|
`readme.md` | this getting started guide
|
||||||
|
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- Open a new terminal and run `cds watch`
|
||||||
|
- (in VS Code simply choose _**Terminal** > Run Task > cds watch_)
|
||||||
|
- Start adding content, for example, a [db/schema.cds](db/schema.cds).
|
||||||
|
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
Learn more at https://cap.cloud.sap/docs/get-started/.
|
||||||
@@ -11,6 +11,7 @@ DateOfBirth = Date of Birth
|
|||||||
DateOfDeath = Date of Death
|
DateOfDeath = Date of Death
|
||||||
PlaceOfBirth = Place of Birth
|
PlaceOfBirth = Place of Birth
|
||||||
PlaceOfDeath = Place of Death
|
PlaceOfDeath = Place of Death
|
||||||
|
Age = Age
|
||||||
Authors = Authors
|
Authors = Authors
|
||||||
Order = Order
|
Order = Order
|
||||||
Orders = Orders
|
Orders = Orders
|
||||||
@@ -6,6 +6,7 @@ Authors = Autoren
|
|||||||
Author = Autor
|
Author = Autor
|
||||||
AuthorID = ID des Autors
|
AuthorID = ID des Autors
|
||||||
AuthorName = Name des Autors
|
AuthorName = Name des Autors
|
||||||
|
Age = Alter
|
||||||
Name = Name
|
Name = Name
|
||||||
Stock = Bestand
|
Stock = Bestand
|
||||||
Order = Bestellung
|
Order = Bestellung
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
applications: {
|
applications: {
|
||||||
"browse-books": {
|
"browse-books": {
|
||||||
title: "Browse Books",
|
title: "Browse Books",
|
||||||
description: "... testing FE v42",
|
description: "w/ SAP Fiori Elements",
|
||||||
additionalInformation: "SAPUI5.Component=bookshop",
|
additionalInformation: "SAPUI5.Component=bookshop",
|
||||||
applicationType : "URL",
|
applicationType : "URL",
|
||||||
url: "/browse/webapp",
|
url: "/browse/webapp",
|
||||||
@@ -21,15 +21,15 @@
|
|||||||
},
|
},
|
||||||
"manage-books": {
|
"manage-books": {
|
||||||
title: "Manage Books",
|
title: "Manage Books",
|
||||||
description: "... testing FE v42",
|
description: "w/ SAP Fiori Elements",
|
||||||
additionalInformation: "SAPUI5.Component=admin",
|
additionalInformation: "SAPUI5.Component=admin",
|
||||||
applicationType : "URL",
|
applicationType : "URL",
|
||||||
url: "/admin/webapp",
|
url: "/admin/webapp",
|
||||||
navigationMode: "embedded"
|
navigationMode: "embedded"
|
||||||
},
|
},
|
||||||
"manage-orders": {
|
"manage-orders": {
|
||||||
title: "Order Books",
|
title: "Manage Orders",
|
||||||
description: "... testing FE v42",
|
description: "w/ SAP Fiori Elements",
|
||||||
additionalInformation: "SAPUI5.Component=orders",
|
additionalInformation: "SAPUI5.Component=orders",
|
||||||
applicationType : "URL",
|
applicationType : "URL",
|
||||||
url: "/orders/webapp",
|
url: "/orders/webapp",
|
||||||
@@ -39,9 +39,8 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
|
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
|
||||||
<!-- <script src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js" -->
|
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
|
||||||
<script src="https://sapui5.hana.ondemand.com/1.78.6/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_fiori_3"
|
data-sap-ui-theme="sap_fiori_3"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using AdminService from '@capire/bookshop';
|
using { AdminService } from '../../db/schema';
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -39,6 +39,27 @@ annotate AdminService.Books with @(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
annotate AdminService.Authors with @(
|
||||||
|
UI: {
|
||||||
|
HeaderInfo: {
|
||||||
|
Description: {Value: lifetime}
|
||||||
|
},
|
||||||
|
Facets: [
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Books}', Target: 'books/@UI.LineItem'},
|
||||||
|
],
|
||||||
|
FieldGroup#Details: {
|
||||||
|
Data: [
|
||||||
|
{Value: placeOfBirth},
|
||||||
|
{Value: placeOfDeath},
|
||||||
|
{Value: dateOfBirth},
|
||||||
|
{Value: dateOfDeath},
|
||||||
|
{Value: age, Label: '{i18n>Age}'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
3
multitenancy/app/bookshop.html
Normal file
3
multitenancy/app/bookshop.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<head>
|
||||||
|
<meta http-equiv="refresh" content="0;url=bookshop/index.html">
|
||||||
|
</head>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using CatalogService from '@capire/bookshop';
|
using CatalogService from '../../srv/cat-service';
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -7,6 +7,8 @@ using CatalogService from '@capire/bookshop';
|
|||||||
annotate CatalogService.Books with @(
|
annotate CatalogService.Books with @(
|
||||||
UI: {
|
UI: {
|
||||||
HeaderInfo: {
|
HeaderInfo: {
|
||||||
|
TypeName: 'Book',
|
||||||
|
TypeNamePlural: 'Books',
|
||||||
Description: {Value: author}
|
Description: {Value: author}
|
||||||
},
|
},
|
||||||
HeaderFacets: [
|
HeaderFacets: [
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
Common Annotations shared by all apps
|
Common Annotations shared by all apps
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using { sap.capire.bookshop as my } from '@capire/bookshop';
|
using { sap.capire.bookshop as my } from '../db/schema';
|
||||||
using { sap.common } from '@capire/common';
|
using { sap.common } from '../db/capire_common';
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -1,44 +1,27 @@
|
|||||||
using OrdersService from '@capire/orders/srv/orders-service';
|
|
||||||
|
|
||||||
annotate OrdersService.Books with {
|
|
||||||
price @Common.FieldControl: #ReadOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Common
|
// Note: this is designed for the OrdersService being co-located with
|
||||||
|
// bookshop. It does not work if OrdersService is run as a separate
|
||||||
|
// process, and is not intended to do so.
|
||||||
//
|
//
|
||||||
annotate OrdersService.OrderItems with {
|
////////////////////////////////////////////////////////////////////////////
|
||||||
book @(
|
|
||||||
Common: {
|
|
||||||
Text: book.title,
|
|
||||||
FieldControl: #Mandatory
|
using { OrdersService } from '../../srv/orders-service';
|
||||||
},
|
|
||||||
ValueList.entity:'Books',
|
|
||||||
);
|
|
||||||
amount @(
|
|
||||||
Common.FieldControl: #Mandatory
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@odata.draft.enabled
|
@odata.draft.enabled
|
||||||
annotate OrdersService.Orders with @(
|
annotate OrdersService.Orders with @(
|
||||||
UI: {
|
UI: {
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Lists of Orders
|
|
||||||
//
|
|
||||||
SelectionFields: [ createdAt, createdBy ],
|
SelectionFields: [ createdAt, createdBy ],
|
||||||
LineItem: [
|
LineItem: [
|
||||||
{Value: createdBy, Label:'Customer'},
|
{Value: OrderNo, Label:'OrderNo'},
|
||||||
|
// {Value: buyer, Label:'Customer'}, we do not need this, because we want extend it later
|
||||||
{Value: createdAt, Label:'Date'}
|
{Value: createdAt, Label:'Date'}
|
||||||
],
|
],
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Order Details
|
|
||||||
//
|
|
||||||
HeaderInfo: {
|
HeaderInfo: {
|
||||||
TypeName: 'Order', TypeNamePlural: 'Orders',
|
TypeName: 'Order', TypeNamePlural: 'Orders',
|
||||||
Title: {
|
Title: {
|
||||||
@@ -85,36 +68,25 @@ annotate OrdersService.Orders with @(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
//The enity types name is OrdersService.my_bookshop_OrderItems
|
annotate OrdersService.Orders_Items with @(
|
||||||
//The annotations below are not generated in edmx WHY?
|
|
||||||
annotate OrdersService.OrderItems with @(
|
|
||||||
UI: {
|
UI: {
|
||||||
HeaderInfo: {
|
|
||||||
TypeName: 'Order Item', TypeNamePlural: ' ',
|
|
||||||
Title: {
|
|
||||||
Value: book.title
|
|
||||||
},
|
|
||||||
Description: {Value: book.descr}
|
|
||||||
},
|
|
||||||
// There is no filterbar for items so the selctionfileds is not needed
|
|
||||||
SelectionFields: [ book_ID ],
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Lists of OrderItems
|
|
||||||
//
|
|
||||||
LineItem: [
|
LineItem: [
|
||||||
{Value: book_ID, Label:'Book'},
|
{Value: product_ID, Label:'Product ID'},
|
||||||
//The following entry is only used to have the assoication followed in the read event
|
{Value: title, Label:'Product Title'},
|
||||||
{Value: book.price, Label:'Book Price'},
|
{Value: price, Label:'Unit Price'},
|
||||||
{Value: amount, Label:'Quantity'},
|
{Value: amount, Label:'Quantity'},
|
||||||
],
|
],
|
||||||
Identification: [ //Is the main field group
|
Identification: [ //Is the main field group
|
||||||
//{Value: ID, Label:'ID'}, //A guid shouldn't be on the UI
|
|
||||||
{Value: book_ID, Label:'Book'},
|
|
||||||
{Value: amount, Label:'Amount'},
|
{Value: amount, Label:'Amount'},
|
||||||
|
{Value: title, Label:'Product'},
|
||||||
|
{Value: price, Label:'Unit Price'},
|
||||||
],
|
],
|
||||||
Facets: [
|
Facets: [
|
||||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>OrderItems}', Target: '@UI.Identification'},
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>OrderItems}', Target: '@UI.Identification'},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
);
|
) {
|
||||||
|
amount @(
|
||||||
|
Common.FieldControl: #Mandatory
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
"name": "sap.fe.templates.ObjectPage",
|
"name": "sap.fe.templates.ObjectPage",
|
||||||
"options": {
|
"options": {
|
||||||
"settings" : {
|
"settings" : {
|
||||||
"entitySet": "OrderItems"
|
"entitySet": "Orders_Items"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
19
multitenancy/app/package.json
Normal file
19
multitenancy/app/package.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "approuter",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"devDependencies": {},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node node_modules/@sap/approuter/approuter.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@sap/approuter": ">=9.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
multitenancy/app/reviews.html
Normal file
3
multitenancy/app/reviews.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<head>
|
||||||
|
<meta http-equiv="refresh" content="0;url=reviews/index.html">
|
||||||
|
</head>
|
||||||
@@ -7,4 +7,5 @@ using from './browse/fiori-service';
|
|||||||
using from './orders/fiori-service';
|
using from './orders/fiori-service';
|
||||||
using from './common';
|
using from './common';
|
||||||
|
|
||||||
using from '@capire/common';
|
using from '../db/capire_common';
|
||||||
|
|
||||||
28
multitenancy/app/xs-app.json
Normal file
28
multitenancy/app/xs-app.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"welcomeFile": "/admin-fiori.html",
|
||||||
|
"authenticationMethod": "route",
|
||||||
|
"sessionTimeout": 30,
|
||||||
|
"logout": {
|
||||||
|
"logoutEndpoint": "/do/logout",
|
||||||
|
"logoutPage": "/"
|
||||||
|
},
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"source": "^/((admin|browse|orders)/webapp/.*)$",
|
||||||
|
"target": "$1",
|
||||||
|
"localDir": ".",
|
||||||
|
"authenticationType": "xsuaa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "^(/[^/]+\\.html(\\?.*)?)$",
|
||||||
|
"target": "$1",
|
||||||
|
"localDir": ".",
|
||||||
|
"authenticationType": "xsuaa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "^.*$",
|
||||||
|
"destination": "srv-api",
|
||||||
|
"authenticationType": "xsuaa"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
45
multitenancy/db/capire_common.cds
Normal file
45
multitenancy/db/capire_common.cds
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using { sap } from '@sap/cds/common';
|
||||||
|
|
||||||
|
extend sap.common.Currencies with {
|
||||||
|
// Currencies.code = ISO 4217 alphabetic three-letter code
|
||||||
|
// with the first two letters being equal to ISO 3166 alphabetic country codes
|
||||||
|
// See also:
|
||||||
|
// [1] https://www.iso.org/iso-4217-currency-codes.html
|
||||||
|
// [2] https://www.currency-iso.org/en/home/tables/table-a1.html
|
||||||
|
// [3] https://www.ibm.com/support/knowledgecenter/en/SSZLC2_7.0.0/com.ibm.commerce.payments.developer.doc/refs/rpylerl2mst97.htm
|
||||||
|
numcode : Integer;
|
||||||
|
exponent : Integer; //> e.g. 2 --> 1 Dollar = 10^2 Cent
|
||||||
|
minor : String; //> e.g. 'Cent'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Code Lists below are designed as optional extensions to
|
||||||
|
* the base schema. Switch them on by adding an Association to
|
||||||
|
* one of the code list entities in your models or by:
|
||||||
|
* annotate sap.common.Countries with @cds.persistence.skip:false;
|
||||||
|
*/
|
||||||
|
|
||||||
|
context sap.common_countries {
|
||||||
|
|
||||||
|
extend sap.common.Countries {
|
||||||
|
regions : Composition of many Regions on regions._parent = $self.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity Regions : sap.common.CodeList {
|
||||||
|
key code : String(5); // ISO 3166-2 alpha5 codes, e.g. DE-BW
|
||||||
|
children : Composition of many Regions on children._parent = $self.code;
|
||||||
|
cities : Composition of many Cities on cities.region = $self;
|
||||||
|
_parent : String(11);
|
||||||
|
}
|
||||||
|
entity Cities : sap.common.CodeList {
|
||||||
|
key code : String(11);
|
||||||
|
region : Association to Regions;
|
||||||
|
districts : Composition of many Districts on districts.city = $self;
|
||||||
|
}
|
||||||
|
entity Districts : sap.common.CodeList {
|
||||||
|
key code : String(11);
|
||||||
|
city : Association to Cities;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
5
multitenancy/db/data/sap.capire.bookshop-Authors.csv
Normal file
5
multitenancy/db/data/sap.capire.bookshop-Authors.csv
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ID;name;dateOfBirth;placeOfBirth;dateOfDeath;placeOfDeath
|
||||||
|
101;Emily Brontë;1818-07-30;Thornton, Yorkshire;1848-12-19;Haworth, Yorkshire
|
||||||
|
107;Charlotte Brontë;1818-04-21;Thornton, Yorkshire;1855-03-31;Haworth, Yorkshire
|
||||||
|
150;Edgar Allen Poe;1809-01-19;Boston, Massachusetts;1849-10-07;Baltimore, Maryland
|
||||||
|
170;Richard Carpenter;1929-08-14;King’s Lynn, Norfolk;2012-02-26;Hertfordshire, England
|
||||||
|
500 Internal Server Error
Gitea Version: 1.24.4 |