Compare commits

..

80 Commits

Author SHA1 Message Date
Daniel
d073403010 cosmetics 2021-02-26 09:30:56 +01:00
Daniel
2552269cec Samples for build-tasks 2021-02-26 09:19:26 +01:00
Daniel
d368eb2ff5 fixed dependency -> never use "latest" 2021-02-19 16:34:15 +01:00
Christian Georgi
3cbb199870 Recommend CodeTours extension 2021-02-19 12:55:35 +01:00
Daniel
b51a08bf4e fixed .registry 2021-02-19 12:40:26 +01:00
Iwona Hahn
b31efc8083 minor edits 2021-02-17 14:07:42 +01:00
Christian Georgi
6669b983b1 First code tour with an overview of the sample repo 2021-02-17 14:07:42 +01:00
Daniel
b6e5a2fced Removed demo 2021-02-17 12:32:27 +01:00
Daniel
6de09e0940 Make usage of @capire/common optional 2021-02-17 12:32:27 +01:00
Daniel
b6f3914d79 Minor cleanup to npm registry mock 2021-02-17 12:32:27 +01:00
Daniel
28402c58b3 Simplified redirects to imported apps 2021-02-17 12:32:27 +01:00
Daniel
77de0e445e demo -> done 2021-02-17 12:32:27 +01:00
Daniel
a037d92c97 demo for bhagat 2021-02-17 12:32:27 +01:00
Christian Georgi
b5031588ce Remove Node 10 for now 2021-02-09 17:15:24 +01:00
Christian Georgi
85319d9e8d Pin sqlite3 to 5.0.0
5.0.1 is broken at the moment
2021-02-09 17:15:24 +01:00
Iwona Hahn
39872200ae Update README.md 2021-02-01 14:54:55 +01:00
Iwona Hahn
6a4af929f1 minor updates 2021-01-27 08:49:33 +01:00
Christian Georgi
5b966c503c Add hint for authentication
So that fewer users stumble over the basic authentication popup.
2021-01-18 14:40:24 +01:00
Christian Georgi
75628b6096 Use latest UI5 again 2021-01-18 14:31:57 +01:00
Daniel
c12e516f5d Prep for upcomming releases 2021-01-13 16:30:40 +01:00
Marius Obert
01073fd6a5 Restructure sentence 2021-01-11 10:54:59 +01:00
Christian Georgi
dc72442764 Better brand name 2020-12-18 15:09:38 +01:00
Manuel Blechschmidt
7e04f50852 Added installation for cds (#177)
When running these samples for the first time,
it is necessary to install the cds binaries globally.

Co-authored-by: Christian Georgi <chgeo@users.noreply.github.com>
Co-authored-by: Daniel Hutzel <daniel.hutzel@sap.com>
2020-12-18 09:25:11 +01:00
Daniel
ea6e274810 Fixed: missing await srv.emit 2020-12-17 16:39:06 +01:00
Christian Georgi
86e5c429bd Example for draft choreography 2020-12-16 17:58:03 +01:00
Daniel
f32398ba8d Using cds.User.default in 4.4.4 2020-12-07 12:41:22 +01:00
Daniel
684c2d53f1 Preparing test for upcomming release 4.4.4 2020-12-03 14:02:15 +01:00
Daniel
b4594e23c5 remove .db file 2020-11-23 14:23:36 +01:00
Daniel
b6028721af make tests more robust 2020-11-22 10:57:13 +01:00
Daniel
e15a6192b6 Aligned orders w/ managed compositions 2020-11-21 01:32:39 +01:00
Daniel
d1eb14f638 Minor Cleanup 2020-11-20 17:22:20 +01:00
Daniel
394a8b5d12 Revised layout of .html pages 2020-11-20 16:11:33 +01:00
Daniel
dae8e96fe1 Adding Vue.js apps for reviews service 2020-11-20 16:11:33 +01:00
Daniel
8f01bf911e Using fake Products entity in @capire/orders 2020-11-20 12:53:25 +01:00
Daniel Hutzel
932f56812c Update server.js 2020-11-17 20:32:47 +01:00
Daniel
d80084bfb7 Added @capire:registry 2020-11-17 17:51:18 +01:00
Daniel
f2c37ec162 composites 2020-11-17 07:35:43 +01:00
Daniel Hutzel
d0d08b1ee1 Enable authorizations w/ dummy-auth (#158)
* Enable authorizations w/ dummy-auth

* fixed: some tests run in privileged mode

* Fixed tests to skip auth

* npm test --silent

* Added dependency to passport
2020-11-12 23:56:04 +01:00
Daniel
ffaec7aa07 Using sqlite 5 2020-11-12 17:06:50 +01:00
Christian Georgi
1587521bd3 Use newer syntax 2020-11-05 09:40:18 +01:00
Andre Meyering
c04c423c0d cat-services.cds: Use "type of" to use "Books:id"
The syntax `Books.id` (dot) is deprecated and is warned about in the latest
CDS releases.  It should be replaced by either `Books:id` (colon, supported
since cds-compiler v1.41.0) or `type of Books:id` (tested with cds-compiler
v1.20.0 from November 2019).
2020-11-05 09:40:18 +01:00
Daniel
7b5c60c3c7 cleanup mocha settings 2020-10-29 17:35:57 +01:00
Christian Georgi
8317503b20 Update issue template 2020-10-29 15:39:38 +01:00
Christian Georgi
f6c838e6ea Update question--feedback-or-bug-.md 2020-10-29 15:35:06 +01:00
Christian Georgi
5d64ca3555 Use test API from cds lib instead of custom one 2020-10-29 11:12:18 +01:00
Daniel
392106d44a Quick fix for affected rows 2020-10-28 16:55:05 +01:00
Daniel
df55110b9b Updated to use srv-to-srv + declared events 2020-10-28 15:17:28 +01:00
Daniel
d9a06d16f1 clean up legacy stuff 2020-10-26 16:21:30 +01:00
Daniel
c424517770 fixing reviews sample 2020-10-22 18:11:46 +02:00
Christian Georgi
363e9aa9a8 Update requests.http 2020-10-12 15:17:03 +02:00
Christian Georgi
7131c13500 Add bookshop image streaming impl 2020-10-12 15:17:03 +02:00
Daniel
934a00eff1 updated gaps 2020-10-07 23:12:48 +02:00
Christian Georgi
a238390b73 Add Node.js 14 to CI 2020-09-30 11:00:21 +02:00
Christian Georgi
54e170ef88 Fix contact name 2020-09-30 10:56:50 +02:00
Christian Georgi
df3cfbb956 Add license badge 2020-09-30 09:40:49 +02:00
Christian Georgi
14756e47f7 Add license files compliant to reuse tool
See https://github.com/fsfe/reuse-tool
2020-09-30 09:24:44 +02:00
Gregor Wolf
90edc4289f Add REST Client test for media 2020-09-30 08:55:29 +02:00
Gregor Wolf
7ae618d803 Add media to scripts and dependencies 2020-09-30 08:55:29 +02:00
Iwona Hahn
e19447b700 update cat-service.js
based on feedback in the community, see also [PR](https://github.wdf.sap.corp/cap/cap.github.wdf.sap.corp/pull/1864)
2020-09-29 18:55:45 +02:00
Dr. David Kunz
ab18c12a69 Merge pull request #135 from SAP-samples/messaging-cds-4
new messaging
2020-09-15 19:03:06 +02:00
D065023
a456eae8ba Merge branch 'messaging-cds-4' of https://github.com/SAP-samples/cloud-cap-samples into messaging-cds-4 2020-09-15 16:36:23 +02:00
D065023
d8308fe7a3 review/reviewed -> reviewed 2020-09-15 16:36:04 +02:00
Dr. David Kunz
a75f8bdc45 Update reviews/srv/reviews-service.js
Co-authored-by: Daniel Hutzel <daniel.hutzel@sap.com>
2020-09-15 16:35:24 +02:00
D065023
a71aaf75a1 new messaging 2020-09-15 15:30:14 +02:00
Daniel Hutzel
6fdd91b8c8 Merge pull request #130 from SAP-samples/test-for-4.2
Updated tests for 4.2
2020-08-27 13:29:24 +02:00
Daniel
62c3969185 Updated tests for 4.2 2020-08-27 13:26:46 +02:00
Christian Georgi
2d608cd882 Add script IDs for UI5 and Fiori bootstrap
Fixes the CORS issues with the hana.ondemand servers
2020-08-25 11:02:02 +02:00
Daniel Hutzel
ae8fe083ed Merge pull request #127 from SAP-samples/cds.test
Prepare moving test kit over to @sap/cds
2020-08-19 12:38:38 +02:00
Daniel
6b3d9db4e9 Remove obsolete dep to @capire/tests 2020-08-19 12:28:53 +02:00
Daniel
332fae3761 Also monkey-patching cds.load 2020-08-19 10:06:36 +02:00
Daniel
039f62209c Prepare moving test kit over to @sap/cds 2020-08-19 09:38:07 +02:00
Daniel Hutzel
a43ade103c Merge pull request #126 from SAP-samples/fix-call-next
Fixing tests to prefer proper calls to next()
2020-08-12 14:00:20 +02:00
Daniel
a6f1d48670 Fixing tests to prefer proper calls to next() 2020-08-12 13:56:49 +02:00
Daniel Hutzel
bd65af43eb Merge pull request #125 from SAP-samples/simplified-reviewed
Simplified reviewed/server.js
2020-08-10 23:24:51 +02:00
Daniel
6f9133cd4f Simplified reviewed/server.js 2020-08-06 19:32:07 +02:00
Daniel Hutzel
441c82b4c9 Merge pull request #123 from SAP-samples/Pinning-Fiori-to-last-known-good
Pinning Fiori to last-known-good
2020-08-01 14:47:30 +02:00
Daniel
fa7cff4123 Pinning Fiori to last-known-good 2020-08-01 14:44:13 +02:00
Christian Georgi
1b69064752 Make cuid.ID Core.Computed for now
+ TODO to check w/ Fiori
2020-07-31 17:18:14 +02:00
Christian Georgi
ada05cf279 Cosmetics 2020-07-31 16:28:08 +02:00
Christian Georgi
4b78a8b637 Do no longer use run blocks - removed in cds 4 2020-07-31 16:28:08 +02:00
89 changed files with 1388 additions and 921 deletions

1
.env
View File

@@ -1 +0,0 @@
cds.features.snapi = y

View File

@@ -21,6 +21,7 @@
}, },
"rules": { "rules": {
"no-console": "off", "no-console": "off",
"require-atomic-updates": "off" "require-atomic-updates": "off",
"require-await":"warn"
} }
} }

View File

@@ -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: ''

View File

@@ -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

View File

@@ -1 +0,0 @@
parallel: true

1
.registry/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.tgz

71
.registry/server.js Normal file
View 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
View 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

136
.tours/samples.tour Normal file
View 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](https://cap.cloud.sap)\nYou will learn which features of the programming models 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"
}

View File

@@ -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": [

9
.vscode/launch.json vendored
View File

@@ -4,6 +4,15 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"name": "Attach by Process ID",
"processId": "${command:PickProcess}",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{ {
"name": "bookshop", "name": "bookshop",
"command": "cds watch bookshop", "command": "cds watch bookshop",

425
LICENSE
View File

@@ -1,217 +1,208 @@
Apache License Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/ Version 2.0, January 2004
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION,
AND DISTRIBUTION
1. Definitions.
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by "License" shall mean the terms and conditions for use, reproduction, and distribution
the copyright owner that is granting the License. as defined by Sections 1 through 9 of this document.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition, "Licensor" shall mean the copyright owner or entity authorized by the copyright
"control" means (i) the power, direct or indirect, to cause the owner that is granting the License.
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"Legal Entity" shall mean the union of the acting entity and all other entities
"You" (or "Your") shall mean an individual or Legal Entity that control, are controlled by, or are under common control with that entity.
exercising permissions granted by this License. For the purposes of this definition, "control" means (i) the power, direct
or indirect, to cause the direction or management of such entity, whether
"Source" form shall mean the preferred form for making modifications, by contract or otherwise, or (ii) ownership of fifty percent (50%) or more
including but not limited to software source code, documentation of the outstanding shares, or (iii) beneficial ownership of such entity.
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but "You" (or "Your") shall mean an individual or Legal Entity exercising permissions
not limited to compiled object code, generated documentation, granted by this License.
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a "Source" form shall mean the preferred form for making modifications, including
copyright notice that is included in or attached to the work but not limited to software source code, documentation source, and configuration
(an example is provided in the Appendix below). files.
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications "Object" form shall mean any form resulting from mechanical transformation
represent, as a whole, an original work of authorship. For the purposes or translation of a Source form, including but not limited to compiled object
of this License, Derivative Works shall not include works that remain code, generated documentation, and conversions to other media types.
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including "Work" shall mean the work of authorship, whether in Source or Object form,
the original version of the Work and any modifications or additions made available under the License, as indicated by a copyright notice that
to that Work or Derivative Works thereof, that is intentionally is included in or attached to the work (an example is provided in the Appendix
submitted to Licensor for inclusion in the Work by the copyright owner below).
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to "Derivative Works" shall mean any work, whether in Source or Object form,
communication on electronic mailing lists, source code control systems, that is based on (or derived from) the Work and for which the editorial revisions,
and issue tracking systems that are managed by, or on behalf of, the annotations, elaborations, or other modifications represent, as a whole, an
Licensor for the purpose of discussing and improving the Work, but original work of authorship. For the purposes of this License, Derivative
excluding communication that is conspicuously marked or otherwise Works shall not include works that remain separable from, or merely link (or
designated in writing by the copyright owner as "Not a Contribution." bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work. "Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative
2. Grant of Copyright License. Subject to the terms and conditions of Works thereof, that is intentionally submitted to Licensor for inclusion in
this License, each Contributor hereby grants to You a perpetual, the Work by the copyright owner or by an individual or Legal Entity authorized
worldwide, non-exclusive, no-charge, royalty-free, irrevocable to submit on behalf of the copyright owner. For the purposes of this definition,
copyright license to reproduce, prepare Derivative Works of, "submitted" means any form of electronic, verbal, or written communication
publicly display, publicly perform, sublicense, and distribute the sent to the Licensor or its representatives, including but not limited to
Work and such Derivative Works in Source or Object form. communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor
3. Grant of Patent License. Subject to the terms and conditions of for the purpose of discussing and improving the Work, but excluding communication
this License, each Contributor hereby grants to You a perpetual, that is conspicuously marked or otherwise designated in writing by the copyright
worldwide, non-exclusive, no-charge, royalty-free, irrevocable owner as "Not a Contribution."
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
Contribution(s) alone or by combination of their Contribution(s) of whom a Contribution has been received by Licensor and subsequently incorporated
with the Work to which such Contribution(s) was submitted. If You within the Work.
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work 2. Grant of Copyright License. Subject to the terms and conditions of this
or a Contribution incorporated within the Work constitutes direct License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
or contributory patent infringement, then any patent licenses no-charge, royalty-free, irrevocable copyright license to reproduce, prepare
granted to You under this License for that Work shall terminate Derivative Works of, publicly display, publicly perform, sublicense, and distribute
as of the date such litigation is filed. the Work and such Derivative Works in Source or Object form.
4. Redistribution. You may reproduce and distribute copies of the 3. Grant of Patent License. Subject to the terms and conditions of this License,
Work or Derivative Works thereof in any medium, with or without each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
modifications, and in Source or Object form, provided that You no-charge, royalty-free, irrevocable (except as stated in this section) patent
meet the following conditions: license to make, have made, use, offer to sell, sell, import, and otherwise
transfer the Work, where such license applies only to those patent claims
(a) You must give any other recipients of the Work or licensable by such Contributor that are necessarily infringed by their Contribution(s)
Derivative Works a copy of this License; and alone or by combination of their Contribution(s) with the Work to which such
Contribution(s) was submitted. If You institute patent litigation against
(b) You must cause any modified files to carry prominent notices any entity (including a cross-claim or counterclaim in a lawsuit) alleging
stating that You changed the files; and that the Work or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses granted to You
(c) You must retain, in the Source form of any Derivative Works under this License for that Work shall terminate as of the date such litigation
that You distribute, all copyright, patent, trademark, and is filed.
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of 4. Redistribution. You may reproduce and distribute copies of the Work or
the Derivative Works; and Derivative Works thereof in any medium, with or without modifications, and
in Source or Object form, provided that You meet the following conditions:
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must (a) You must give any other recipients of the Work or Derivative Works a copy
include a readable copy of the attribution notices contained of this License; and
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one (b) You must cause any modified files to carry prominent notices stating that
of the following places: within a NOTICE text file distributed You changed the files; and
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or, (c) You must retain, in the Source form of any Derivative Works that You distribute,
within a display generated by the Derivative Works, if and all copyright, patent, trademark, and attribution notices from the Source
wherever such third-party notices normally appear. The contents form of the Work, excluding those notices that do not pertain to any part
of the NOTICE file are for informational purposes only and of the Derivative Works; and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside (d) If the Work includes a "NOTICE" text file as part of its distribution,
or as an addendum to the NOTICE text from the Work, provided then any Derivative Works that You distribute must include a readable copy
that such additional attribution notices cannot be construed of the attribution notices contained within such NOTICE file, excluding those
as modifying the License. notices that do not pertain to any part of the Derivative Works, in at least
one of the following places: within a NOTICE text file distributed as part
You may add Your own copyright statement to Your modifications and of the Derivative Works; within the Source form or documentation, if provided
may provide additional or different license terms and conditions along with the Derivative Works; or, within a display generated by the Derivative
for use, reproduction, or distribution of Your modifications, or Works, if and wherever such third-party notices normally appear. The contents
for any such Derivative Works as a whole, provided Your use, of the NOTICE file are for informational purposes only and do not modify the
reproduction, and distribution of the Work otherwise complies with License. You may add Your own attribution notices within Derivative Works
the conditions stated in this License. that You distribute, alongside or as an addendum to the NOTICE text from the
Work, provided that such additional attribution notices cannot be construed
5. Submission of Contributions. Unless You explicitly state otherwise, as modifying the License.
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of You may add Your own copyright statement to Your modifications and may provide
this License, without any additional terms or conditions. additional or different license terms and conditions for use, reproduction,
Notwithstanding the above, nothing herein shall supersede or modify or distribution of Your modifications, or for any such Derivative Works as
the terms of any separate license agreement you may have executed a whole, provided Your use, reproduction, and distribution of the Work otherwise
with Licensor regarding such Contributions. complies with the conditions stated in this License.
6. Trademarks. This License does not grant permission to use the trade 5. Submission of Contributions. Unless You explicitly state otherwise, any
names, trademarks, service marks, or product names of the Licensor, Contribution intentionally submitted for inclusion in the Work by You to the
except as required for reasonable and customary use in describing the Licensor shall be under the terms and conditions of this License, without
origin of the Work and reproducing the content of the NOTICE file. any additional terms or conditions. Notwithstanding the above, nothing herein
shall supersede or modify the terms of any separate license agreement you
7. Disclaimer of Warranty. Unless required by applicable law or may have executed with Licensor regarding such Contributions.
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS, 6. Trademarks. This License does not grant permission to use the trade names,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or trademarks, service marks, or product names of the Licensor, except as required
implied, including, without limitation, any warranties or conditions for reasonable and customary use in describing the origin of the Work and
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A reproducing the content of the NOTICE file.
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any 7. Disclaimer of Warranty. Unless required by applicable law or agreed to
risks associated with Your exercise of permissions under this License. in writing, Licensor provides the Work (and each Contributor provides its
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8. Limitation of Liability. In no event and under no legal theory, KIND, either express or implied, including, without limitation, any warranties
whether in tort (including negligence), contract, or otherwise, or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR
unless required by applicable law (such as deliberate and grossly A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness
negligent acts) or agreed to in writing, shall any Contributor be of using or redistributing the Work and assume any risks associated with Your
liable to You for damages, including any direct, indirect, special, exercise of permissions under this License.
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the 8. Limitation of Liability. In no event and under no legal theory, whether
Work (including but not limited to damages for loss of goodwill, in tort (including negligence), contract, or otherwise, unless required by
work stoppage, computer failure or malfunction, or any and all applicable law (such as deliberate and grossly negligent acts) or agreed to
other commercial damages or losses), even if such Contributor in writing, shall any Contributor be liable to You for damages, including
has been advised of the possibility of such damages. any direct, indirect, special, incidental, or consequential damages of any
character arising as a result of this License or out of the use or inability
9. Accepting Warranty or Additional Liability. While redistributing to use the Work (including but not limited to damages for loss of goodwill,
the Work or Derivative Works thereof, You may choose to offer, work stoppage, computer failure or malfunction, or any and all other commercial
and charge a fee for, acceptance of support, warranty, indemnity, damages or losses), even if such Contributor has been advised of the possibility
or other liability obligations and/or rights consistent with this of such damages.
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf 9. Accepting Warranty or Additional Liability. While redistributing the Work
of any other Contributor, and only if You agree to indemnify, or Derivative Works thereof, You may choose to offer, and charge a fee for,
defend, and hold each Contributor harmless for any liability acceptance of support, warranty, indemnity, or other liability obligations
incurred by, or claims asserted against, such Contributor by reason and/or rights consistent with this License. However, in accepting such obligations,
of your accepting any such warranty or additional liability. You may act only on Your own behalf and on Your sole responsibility, not on
behalf of any other Contributor, and only if You agree to indemnify, defend,
END OF TERMS AND CONDITIONS and hold each Contributor harmless for any liability incurred by, or claims
asserted against, such Contributor by reason of your accepting any such warranty
APPENDIX: How to apply the Apache License to your work. or additional liability. END OF TERMS AND CONDITIONS
To apply the Apache License to your work, attach the following APPENDIX: How to apply the Apache License to your work.
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include To apply the Apache License to your work, attach the following boilerplate
the brackets!) The text should be enclosed in the appropriate notice, with the fields enclosed by brackets "[]" replaced with your own identifying
comment syntax for the file format. We also recommend that a information. (Don't include the brackets!) The text should be enclosed in
file or class name and description of purpose be included on the the appropriate comment syntax for the file format. We also recommend that
same "printed page" as the copyright notice for easier a file or class name and description of purpose be included on the same "printed
identification within third-party archives. page" as the copyright notice for easier identification within third-party
archives.
Copyright [yyyy] [name of copyright owner]
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. Licensed under the Apache License, Version 2.0 (the "License");
You may obtain a copy of the License at
you may not use this file except in compliance with the License.
http://www.apache.org/licenses/LICENSE-2.0
You may obtain a copy of the License at
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, http://www.apache.org/licenses/LICENSE-2.0
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and Unless required by applicable law or agreed to in writing, software
limitations under the License.
distributed under the License is distributed on an "AS IS" BASIS,
--------------------------------------------------------------------------------------------------------------------------
APIs WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
This project may include APIs to SAP or third-party products or services. See the License for the specific language governing permissions and
The use of these APIs, products and services may be subject to additional
agreements. In no event shall the application of the Apache Software limitations under the License.
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.

View File

@@ -3,17 +3,22 @@
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). &rarr; 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). &rarr; See [**Overview** of contained samples](samples.md)
![](https://github.com/SAP-samples/cloud-cap-samples/workflows/CI/badge.svg) ![](https://github.com/SAP-samples/cloud-cap-samples/workflows/CI/badge.svg)
<!--[![REUSE status](https://api.reuse.software/badge/github.com/SAP-samples/cloud-cap-samples)](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 have [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,15 +43,35 @@ 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
```
## 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>
@@ -55,4 +80,4 @@ In case you have a question, find a bug, or otherwise need support, please use o
## 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](LICENSES/Apache-2.0.txt) file.

View File

@@ -1 +0,0 @@
cds.features.snapi = y

View File

@@ -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 }
}
} }
} }

View File

@@ -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>
&nbsp;&nbsp; {{ book.stock }} in stock &nbsp;&nbsp; {{ 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>

View File

@@ -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 {

View File

@@ -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": "^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",

View File

@@ -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) |

View File

@@ -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;
} }

View 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
}

View File

@@ -1,10 +1,14 @@
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 {*, @readonly entity Books as SELECT from my.Books { *,
author.name as author author.name as author
} excluding { createdBy, modifiedBy }; } excluding { createdBy, modifiedBy };
@requires_: 'authenticated-user' @readonly entity ListOfBooks as SELECT from Books
action submitOrder (book : Books.ID, amount: Integer); excluding { descr };
@requires: 'authenticated-user'
action submitOrder ( book: Books:ID, amount: Integer ) returns { stock: Integer };
event OrderedBook : { book: Books:ID; amount: Integer; buyer: String };
} }

View File

@@ -1,20 +1,28 @@
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, tx = cds.tx(req)
const n = await UPDATE (Books, book) let {stock} = await tx.read('stock').from(Books,book)
.with ({ stock: {'-=': amount }}) if (stock >= amount) {
.where ({ stock: {'>=': amount }}) await tx.update (Books,book).with ({ stock: 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','Books', each => {
if (each.stock > 111) each.title += ` -- 11% discount!` if (each.stock > 111) {
each.title += ` -- 11% discount!`
}
}) })
}
return super.init()
}}
module.exports = { CatalogService }

View File

@@ -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

View File

@@ -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": "*"
}
} }

View File

@@ -1 +1,2 @@
cds.features.snapi = y # cds.requires.messaging.kind = file-based-messaging
PORT = 4004

View File

@@ -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,8 +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"
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"

3
fiori/app/bookshop.html Normal file
View File

@@ -0,0 +1,3 @@
<head>
<meta http-equiv="refresh" content="0;url=bookshop/index.html">
</head>

View File

@@ -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: [

View File

@@ -4,7 +4,9 @@
using from './admin/fiori-service'; using from './admin/fiori-service';
using from './browse/fiori-service'; using from './browse/fiori-service';
using from './orders/fiori-service';
using from './common'; using from './common';
using from '@capire/common'; using from '@capire/common';
// only works in case of embedded orders service
using from '@capire/orders/app/orders/fiori-service';

3
fiori/app/reviews.html Normal file
View File

@@ -0,0 +1,3 @@
<head>
<meta http-equiv="refresh" content="0;url=reviews/index.html">
</head>

View File

@@ -1,3 +0,0 @@
// Proxy for importing schema from bookshop sample
using from '@capire/bookshop';
namespace sap.capire.bookshop;

View File

@@ -2,11 +2,13 @@
"name": "@capire/fiori", "name": "@capire/fiori",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@capire/bookshop": "../bookshop", "@capire/bookshop": "*",
"@capire/orders": "../orders", "@capire/reviews": "*",
"@capire/common": "../common", "@capire/orders": "*",
"@sap/cds": ">=3.33.1", "@capire/common": "*",
"express": "^4.17.1" "@sap/cds": "^4",
"express": "^4.17.1",
"passport": "0.4.1"
}, },
"scripts": { "scripts": {
"start": "cds run --in-memory?", "start": "cds run --in-memory?",
@@ -14,6 +16,12 @@
}, },
"cds": { "cds": {
"requires": { "requires": {
"ReviewsService": {
"kind": "odata", "model": "@capire/reviews"
},
"OrdersService": {
"kind": "odata", "model": "@capire/orders"
},
"db": { "db": {
"kind": "sql" "kind": "sql"
} }

18
fiori/server.js Normal file
View File

@@ -0,0 +1,18 @@
const cds = require ('@sap/cds')
cds.once('bootstrap',(app)=>{
app.use ('/orders/webapp', _from('@capire/orders/app/orders/webapp/manifest.json'))
app.use ('/bookshop', _from('@capire/bookshop/app/vue/index.html'))
app.use ('/reviews', _from('@capire/reviews/app/vue/index.html'))
})
cds.once('served', require('./srv/mashup'))
module.exports = cds.server
// -----------------------------------------------------------------------
// Helper for serving static content from npm-installed packages
const {static} = require('express')
const {dirname} = require('path')
const _from = target => static (dirname (require.resolve(target)))

View File

@@ -1,3 +0,0 @@
// Proxy for importing services from bookshop sample
using from '@capire/bookshop';
annotate AdminService with @impl:'srv/admin-service.js';

View File

@@ -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)
})

25
fiori/srv/mashup.cds Normal file
View File

@@ -0,0 +1,25 @@
////////////////////////////////////////////////////////////////////////////
//
// Mashing up imported models...
//
using { sap.capire.bookshop.Books } from '@capire/bookshop';
//
// Extend Books with access to Reviews and average ratings
//
using { ReviewsService.Reviews } from '@capire/reviews';
extend Books with {
reviews : Composition of many Reviews on reviews.subject = $self.ID;
rating : Decimal;
}
//
// Extend Orders with Books as Products
//
using { sap.capire.orders.Orders_Items } from '@capire/orders';
extend Orders_Items with {
book : Association to Books on product.ID = book.ID
}

59
fiori/srv/mashup.js Normal file
View File

@@ -0,0 +1,59 @@
////////////////////////////////////////////////////////////////////////////
//
// Mashing up provided and required services...
//
module.exports = async()=>{ // called by server.js
const cds = require('@sap/cds')
const CatalogService = await cds.connect.to ('CatalogService')
const ReviewsService = await cds.connect.to ('ReviewsService')
const OrdersService = await cds.connect.to ('OrdersService')
const db = await cds.connect.to ('db')
// reflect entity definitions used below...
const { Books } = db.entities ('sap.capire.bookshop')
//
// Delegate requests to read reviews to the ReviewsService
// Note: prepend is neccessary to intercept generic default handler
//
CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => {
console.debug ('> delegating request to ReviewsService')
const [id] = req.params, { columns, limit } = req.query.SELECT
return ReviewsService.tx(req).read ('Reviews',columns).limit(limit).where({subject:String(id)})
}))
//
// Create an order with the OrdersService when CatalogService signals a new order
//
CatalogService.on ('OrderedBook', async (msg) => {
const { book, amount, buyer } = msg.data
const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price })
return OrdersService.tx(msg).create ('Orders').entries({
OrderNo: 'Order at '+ (new Date).toLocaleString(),
Items: [{ product:{ID:`${book}`}, title, price, amount }],
buyer, createdBy: buyer
})
})
//
// Update Books' average ratings when ReviewsService signals updatd reviews
//
ReviewsService.on ('reviewed', (msg) => {
console.debug ('> received:', msg.event, msg.data)
const { subject, rating } = msg.data
return UPDATE(Books,subject).with({rating})
// ^ Note: the framework will execute this and take care for db.tx
})
//
// Reduce stock of ordered books for orders are created from Orders admin UI
//
OrdersService.on ('OrderChanged', (msg) => {
console.debug ('> received:', msg.event, msg.data)
const { product, deltaAmount } = msg.data
return UPDATE (Books) .where ('ID =', product)
.and ('stock >=', deltaAmount)
.set ('stock -=', deltaAmount)
})
}

View File

@@ -0,0 +1,14 @@
const cds = require ('./sap-cds')
module.exports = class extends cds.build.Task {
async build ({src='*'}) {
this.log (`Generating edmx output for '${src}'...`)
const csn = await this.model(src)
return Promise.all (csn.services.map (({name:service}) => {
const edmx = cds.compile(csn).to.edmx({service})
return this.write(edmx).to(`{srv}/src/main/resources/${service}.edmx`)
}))
}
}

View File

@@ -0,0 +1,51 @@
const cds = require ('@sap/cds/lib')
const path = require('path')
const cwd = process.cwd()
const _resolve = (root,file) => path.resolve (cwd, root, file.replace(/{(app|db|srv)}\/?/g, (_,folder) => cds.env.folders[folder]))
const _local = (file) => path.relative (cwd,file)
class BuildTask {
async build (options) {}
async clean (options) {}
async model(src='*') {
return cds.linked (await cds.load(src))
}
log(...args) { return console.log(...args) }
warn(...args) { return console.warn(...args) }
error(...args) { return console.error(...args) }
write(x) {
if (typeof x === 'object') x = JSON.stringify(x,null,' ')
return { to: async (dst)=>{
const file = _resolve (this.options.dest, dst)
await cds.utils.mkdirp (path.dirname (file))
await cds.utils.promises.writeFile (file,x)
console.log ('> wrote:', _local(file))
return file
}}
}
copy(x) {
return { to: async (dst) => {} }
}
}
module.exports = Object.assign (cds, {
build: {
run (tasks, _options) {
const options = { dest:'gen', ..._options }
return Promise.all(tasks.map (async each => {
const task = Object.assign (new each, {options})
await task.build (options)
}))
},
Task: BuildTask
}
})

5
fiori/test/build/test.js Normal file
View File

@@ -0,0 +1,5 @@
const cds = require ('./sap-cds')
const task = require('./build-task')
cds.build.run ([task], {src:process.argv[2]})
.catch(console.error)

65
fiori/test/requests.http Normal file
View File

@@ -0,0 +1,65 @@
@bookshop = http://localhost:4004
@reviews-service = {{bookshop}}/reviews
# Uncomment this when running a separate reviews service
@reviews-service = http://localhost:4005/reviews
#################################################
#
# Reviews Service
#
GET {{reviews-service}}/Reviews
###
POST {{reviews-service}}/Reviews
Authorization: Basic {{$processEnv USER}}:
Content-Type: application/json
{"subject":"201", "title":"boo", "rating":3 }
#################################################
#
# Bookshop Services
#
GET {{bookshop}}/browse/Books/201/reviews?
&$select=rating,date,title
&$top=3
###
GET {{bookshop}}/browse/Books(201)?
&$select=ID,title,rating
&$expand=reviews
#################################################
#
# Orders Service, incl. draft choreography
#
@newOrderID = e939604c-ab83-4d4f-bdb6-95fe30b3773e
GET {{bookshop}}/orders/Orders
### Create order, still inactive
POST {{bookshop}}/orders/Orders
Content-Type: application/json
{"ID": "{{newOrderID}}"}
### Get inactive order. We have to specify `IsActiveEntity`.
GET {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)
### Activate order using `.../<servicename>.draftActivate`
POST {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)/OrdersService.draftActivate
Content-Type: application/json
### Get active order
GET {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=true)

BIN
media/test/Test.png Normal file

Binary file not shown.

35
media/test/media.http Normal file
View 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:

2
orders/.env Normal file
View File

@@ -0,0 +1,2 @@
cds.requires.messaging.kind = file-based-messaging
PORT = 4006

39
orders/app/fiori-app.html Normal file
View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bookshop</title>
<script>
window["sap-ushell-config"] = {
defaultRenderer: "fiori2",
applications: {
"manage-orders": {
title: "Manage Orders",
description: "... testing FE v42",
additionalInformation: "SAPUI5.Component=orders",
applicationType : "URL",
url: "/orders/webapp",
navigationMode: "embedded"
}
}
};
</script>
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
data-sap-ui-compatVersion="edge"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-frameOptions="allow"
></script>
<script>
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
</script>
</head>
<body class="sapUiBody" id="content"></body>
</html>

5
orders/app/index.cds Normal file
View File

@@ -0,0 +1,5 @@
/*
This model controls what gets served to Fiori frontends...
*/
using from './orders/fiori-service';

View File

@@ -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'},
{Value: createdAt, Label:'Date'} {Value: createdAt, Label:'Date'}
], ],
////////////////////////////////////////////////////////////////////////////
//
// Order Details
//
HeaderInfo: { HeaderInfo: {
TypeName: 'Order', TypeNamePlural: 'Orders', TypeName: 'Order', TypeNamePlural: 'Orders',
Title: { Title: {
@@ -62,7 +45,7 @@ annotate OrdersService.Orders with @(
], ],
FieldGroup#Details: { FieldGroup#Details: {
Data: [ Data: [
{Value: currency_code, Label:'Currency'} {Value: currency.code, Label:'Currency'}
] ]
}, },
FieldGroup#Created: { FieldGroup#Created: {
@@ -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
);
};

View File

@@ -121,7 +121,7 @@
"name": "sap.fe.templates.ObjectPage", "name": "sap.fe.templates.ObjectPage",
"options": { "options": {
"settings" : { "settings" : {
"entitySet": "OrderItems" "entitySet": "Orders_Items"
} }
} }
}, },

View File

@@ -1,4 +0,0 @@
ID;amount;parent_ID;book_ID;netAmount
58040e66-1dcd-4ffb-ab10-fdce32028b79;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;201;11.11
64e718c9-ff99-47f1-8ca3-950c850777d4;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;271;15
e9641166-e050-4261-bfee-d1e797e6cb7f;2;64e718c9-ff99-47f1-8ca3-950c850777d4;252;28
Internal Server Error - Bruno's Gitea
500 Internal Server Error

Gitea Version: 1.24.4