Compare commits

..

29 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
39 changed files with 364 additions and 99 deletions

View File

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

View File

@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
node-version: [12.x, 14.x]
steps:
- uses: actions/checkout@v2

1
.registry/.gitignore vendored Normal file
View File

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

View File

@@ -5,9 +5,9 @@ const app = express()
const { PORT=4444 } = process.env
const [,,port=PORT] = process.argv
process.chdir(__dirname)
app.use('/-/:tarball', (req,res,next) => {
const url = decodeURIComponent(req.url)
console.debug ('GET', req.params)
try {
const { tarball } = req.params

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.
"recommendations": [
"SAPSE.vscode-cds",
"sapse.vscode-cds",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"mechatroner.rainbow-csv",
"humao.rest-client",
"alexcvzz.vscode-sqlite",
"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.
"unwantedRecommendations": [

View File

@@ -3,18 +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)
![](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)
<!--[![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
1. [Install @sap/cds-dk](https://cap.cloud.sap/docs/get-started/) as documented in [capire](https://cap.cloud.sap)
2. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/in-vscode)
1. Install [**@sap/cds-dk**](https://cap.cloud.sap/docs/get-started/) globally:
```sh
npm i -g @sap/cds-dk
```
2. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/tools#vscode)
### Download
Clone this repo as shown below, if you have [git](https://git-scm.com/downloads) installed,
otherwise [download as zip file](archive/master.zip).
If you have [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/master.zip).
```sh
git clone https://github.com/sap-samples/cloud-cap-samples samples
@@ -39,9 +43,12 @@ cds watch bookshop
After that open this link in your browser: [http://localhost:4004](http://localhost:4004)
When asked to log in, type `alice` as user and leave the password field blank, which is the [default user](https://cap.cloud.sap/docs/node.js/authentication#mocked).
### Testing
Run the provided tests with [_jest_](http://jestjs.io) or [_mocha_](http://mochajs.org), for example:
```sh
npx jest
```
@@ -50,7 +57,7 @@ npx jest
### Serve `npm`
We've simple npm registry mock included which allows you to do an `npm install @capire/<package>` anywhere locally. Use it as follows:
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
@@ -58,7 +65,8 @@ 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, e.g.:
2. Install one of the @capire packages wherever you like, for example:
```sh
npm add @capire/common @capire/bookshop
```
@@ -72,4 +80,4 @@ In case you have a question, find a bug, or otherwise need support, please use o
## 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](LICENSES/Apache-2.0.txt) 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

@@ -36,7 +36,7 @@
<td class="rating-stars">
{{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }}
</td>
<td>{{ book.currency.symbol }} {{ book.price }}</td>
<td>{{ book.currency && book.currency.symbol }} {{ book.price }}</td>
</tr>
</table>

View File

@@ -5,10 +5,10 @@ This stand-alone sample introduces the essential tasks in the development of CAP
## Hypothetical Use Cases
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.
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
@@ -20,12 +20,12 @@ npm run watch
| Links to capire | Sample files / folders |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| [Project Setup and 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) |
| [Defining Services](https://cap.cloud.sap/docs/guides/providing-services) | [`./srv/*.cds`](./srv) |
| [Single-purposed Services](https://cap.cloud.sap/docs/guides/providing-services#single-purposed-services) | [`./srv/*.cds`](./srv) |
| [Generic Providers](https://cap.cloud.sap/docs/guides/providing-services) | http://localhost:4004 |
| Using Databases | [`./db/data/*.csv`](./db/data) |
| [Project Setup & Layouts](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content) | [`./`](./) |
| [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/services#defining-services) | [`./srv/*.cds`](./srv) |
| [Single-purposed Services](https://cap.cloud.sap/docs/guides/services#single-purposed-services) | [`./srv/*.cds`](./srv) |
| [Providing & Consuming Providers](https://cap.cloud.sap/docs/guides/providing-services) | http://localhost:4004 |
| [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 Tests | [`./test`](./test) |
| [Sharing for Reuse](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content) | [`./index.cds`](./index.cds) |
| Adding Tests | [`./test`](./test) |
| [Sharing for Reuse](https://cap.cloud.sap/docs/guides/reuse-and-compose) | [`./index.cds`](./index.cds) |

View File

View File

@@ -1,12 +1,12 @@
using { sap.capire.bookshop as my } from '../db/schema';
service CatalogService @(path:'/browse') {
@readonly entity Books as SELECT from my.Books {*,
@readonly entity Books as SELECT from my.Books { *,
author.name as author
} excluding { createdBy, modifiedBy };
@readonly entity ListOfBooks as SELECT from Books
excluding { descr, stock };
excluding { descr };
@requires: 'authenticated-user'
action submitOrder ( book: Books:ID, amount: Integer ) returns { stock: Integer };

View File

@@ -1,7 +1,7 @@
const cds = require('@sap/cds')
const { Books } = cds.entities ('sap.capire.bookshop')
class CatalogService extends cds.ApplicationService { async init(){
class CatalogService extends cds.ApplicationService { init(){
// Reduce stock of ordered books if available stock suffices
this.on ('submitOrder', async req => {
@@ -9,7 +9,7 @@ class CatalogService extends cds.ApplicationService { async init(){
let {stock} = await tx.read('stock').from(Books,book)
if (stock >= amount) {
await tx.update (Books,book).with ({ stock: stock -= amount })
this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
await this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
return { stock }
}
else return req.error (409,`${amount} exceeds stock for book #${book}`)

View File

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

@@ -13,7 +13,7 @@
applications: {
"browse-books": {
title: "Browse Books",
description: "... testing FE v42",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=bookshop",
applicationType : "URL",
url: "/browse/webapp",
@@ -21,7 +21,7 @@
},
"manage-books": {
title: "Manage Books",
description: "... testing FE v42",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=admin",
applicationType : "URL",
url: "/admin/webapp",
@@ -29,7 +29,7 @@
},
"manage-orders": {
title: "Manage Orders",
description: "... testing FE v42",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=orders",
applicationType : "URL",
url: "/orders/webapp",
@@ -40,8 +40,7 @@
</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" -->
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/1.78.6/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-compatVersion="edge"
data-sap-ui-theme="sap_fiori_3"

View File

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

View File

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

View File

@@ -1,19 +1,18 @@
const express = require ('express')
const cds = require ('@sap/cds')
cds.once('bootstrap',(app)=>{
const {dirname} = require ('path')
// serving the orders app imported from @capire/orders
const orders_app = dirname (require.resolve('@capire/orders/app/orders/webapp/manifest.json'))
app.use ('/orders/webapp', express.static(orders_app))
// serving the vue.js app imported from @capire/bookshop
const bookshop_app = dirname (require.resolve('@capire/bookshop/app/vue/index.html'))
app.use ('/vue/bookshop', express.static(bookshop_app))
// serving the vue.js app imported from @capire/reviews
const reviews_app = dirname (require.resolve('@capire/reviews/app/vue/index.html'))
app.use ('/vue/reviews', express.static(reviews_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

@@ -49,7 +49,7 @@ module.exports = async()=>{ // called by server.js
//
// Reduce stock of ordered books for orders are created from Orders admin UI
//
OrdersService.on ('OrderChanged', async (msg) => {
OrdersService.on ('OrderChanged', (msg) => {
console.debug ('> received:', msg.event, msg.data)
const { product, deltaAmount } = msg.data
return UPDATE (Books) .where ('ID =', product)

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)

View File

@@ -42,7 +42,24 @@ GET {{bookshop}}/browse/Books(201)?
#################################################
#
# Orders Service
# 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)

View File

@@ -21,6 +21,3 @@ entity Orders_Items {
entity Products @(cds.persistence.skip:'always') {
key ID : String;
}
// Activate extension package
using from '@capire/common';

View File

@@ -2,7 +2,6 @@
"name": "@capire/orders",
"version": "1.0.0",
"dependencies": {
"@capire/common": "*",
"@sap/cds": "^4.3.0"
}
}

View File

@@ -11,7 +11,7 @@ class OrdersService extends cds.ApplicationService {
const { amount:before } = await cds.tx(req).run (
SELECT.one.from (OrderItems, oi => oi.amount) .where ({up__ID:ID, product_ID})
)
if (amount != before) this.orderChanged (product_ID, amount-before)
if (amount != before) await this.orderChanged (product_ID, amount-before)
}
})
@@ -20,7 +20,7 @@ class OrdersService extends cds.ApplicationService {
const Items = await cds.tx(req).run (
SELECT.from (OrderItems, oi => { oi.product_ID, oi.amount }) .where ({up__ID:ID})
)
if (Items) for (let it of Items) this.orderChanged (it.product_ID, -it.amount)
if (Items) await Promise.all (Items.map(it => this.orderChanged (it.product_ID, -it.amount)))
})
return super.init()

View File

@@ -17,10 +17,10 @@
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-subset": "^1.6.0",
"sqlite3": "^5"
"sqlite3": "5.0.0"
},
"scripts": {
"registry": "cd .registry && node server.js",
"registry": "node .registry/server.js",
"bookshop": "cds watch bookshop",
"fiori": "cds watch fiori",
"media": "cds watch media",

View File

@@ -37,7 +37,7 @@ const reviews = new Vue ({
reviews.message = {}
},
async newReview () {
newReview () {
reviews.review = {}
reviews.message = {}
setTimeout (()=> $('form > input').focus(), 111)

View File

@@ -27,7 +27,14 @@ service ReviewsService {
annotate ReviewsService.Reviews with @restrict:[
{ grant:'READ', to:'any' }, // everybody can read reviews
{ grant:'CREATE', to:'authenticated-user' }, // users must login to add reviews
{ grant:'UPDATE', to:'authenticated-user', where:'reviewer=$user' },
/////////////////////////////////////////////////
//
// Temporarily disabling this due to glitch in CAP Node.js runtime:
// { grant:'UPDATE', to:'authenticated-user', where:'reviewer=$user' },
// -> reenable it when the issue is fixed
{ grant:'UPDATE', to:'authenticated-user' },
//
////////////////////////////////////////////////////
{ grant:'DELETE', to:'admin' },
];

View File

@@ -1,5 +1,5 @@
const cds = require ('@sap/cds')
module.exports = cds.service.impl (async function(){
module.exports = cds.service.impl (function(){
// Get the CSN definition for Reviews from the db schema for sub-sequent queries
// ( Note: we explicitly specify the namespace to support embedded reuse )
@@ -16,7 +16,7 @@ module.exports = cds.service.impl (async function(){
SELECT.one (['round(avg(rating),2) as rating']) .from (Reviews) .where ({subject})
)
global.it || console.log ('< emitting:', 'reviewed', { subject, rating })
this.emit ('reviewed', { subject, rating })
await this.emit ('reviewed', { subject, rating })
})
// Increment counter for reviews considered helpful

View File

@@ -1,7 +1,7 @@
# Overview of Samples
The list below gives an overview of the samples provided in subdirectories.
Each sub directory essentially is a individual npm package arranged in an [all-in-one monorepo](all-in-one-monorepo) umbrella setup.
The following list gives an overview of the samples provided in subdirectories.
Each sub directory essentially is an individual npm package arranged in an [all-in-one monorepo](all-in-one-monorepo) umbrella setup.
## [@capire/hello-world](hello)
@@ -13,7 +13,7 @@ Each sub directory essentially is a individual npm package arranged in an [all-i
- [Getting Started](https://cap.cloud.sap/docs/get-started/in-a-nutshell) with CAP, briefly introducing:
- [Project Setup](https://cap.cloud.sap/docs/get-started/) and [Layouts](https://cap.cloud.sap/docs/get-started/projects)
- [Domain Modelling](https://cap.cloud.sap/docs/guides/domain-models)
- [Domain Modeling](https://cap.cloud.sap/docs/guides/domain-models)
- [Defining Services](https://cap.cloud.sap/docs/guides/providing-services)
- [Generic Providers](https://cap.cloud.sap/docs/guides/generic-providers)
- [Adding Custom Logic](https://cap.cloud.sap/docs/guides/service-impl)
@@ -22,7 +22,7 @@ Each sub directory essentially is a individual npm package arranged in an [all-i
## [@capire/common](common)
- Showcases how to extend [@sap/cds/common](https://cap.cloud.sap/docs/cds/common) thereby covering...
- Showcases how to extend [@sap/cds/common](https://cap.cloud.sap/docs/cds/common) thereby covering:
- Building [extension packages](https://cap.cloud.sap/docs/guides/domain-models#aspects-extensibility)
- Providing [reuse packages](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content)
- [Verticalization](https://cap.cloud.sap/docs/cds/common#adapting-to-your-needs)
@@ -32,22 +32,22 @@ Each sub directory essentially is a individual npm package arranged in an [all-i
## [@capire/orders](orders)
- A standalone orders mgmt service, demonstrating...
- A standalone orders management service, demonstrating:
- Using [Compositions](https://cap.cloud.sap/docs/cds/cdl#compositions) in [Domain Models](https://cap.cloud.sap/docs/guides/domain-models), along with
- [Serving deeply nested documents](https://cap.cloud.sap/docs/guides/generic-providers#serving-structured-data)
## [@capire/reviews](reviews)
- Shows how to implement a modular service to manage product reviews, including...
- Shows how to implement a modular service to manage product reviews, including:
- Consuming other services synchronously and asynchronously
- Serving requests synchronously
- Emitting events asynchronously
- Grow as you go, with...
- Grow as you go, with:
- Mocking app services
- Running service meshes
- Late-cut Micro Services
- As well as managed data, input validations and authorization
- As well as managed data, input validations, and authorization
## [@capire/fiori](fiori)
@@ -57,11 +57,11 @@ Each sub directory essentially is a individual npm package arranged in an [all-i
- [@capire/reviews](reviews)
- [@capire/orders](orders)
- [@capire/common](common)
- [Adds a Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookshop, thereby introducing to...
- [Adds a SAP Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookshop, thereby introducing to:
- [OData Annotations](https://cap.cloud.sap/docs/guides/fiori#adding-odata-annotations) in `.cds` files
- Support for [Fiori Draft](https://cap.cloud.sap/docs/guides/fiori#draft)
- Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help)
- Serving Fiori apps locally
- Serving SAP Fiori apps locally
- [The Vue.js app](bookshop/app/vue) imported from bookshop is served as well

View File

@@ -1,5 +1,5 @@
const { expect } = require('../test')
const cds = require('@sap/cds/lib')
const { expect } = cds.test
const CQL = ([cql]) => cds.parse.cql(cql)
const Foo = { name: 'Foo' }
const Books = { name: 'capire.bookshop.Books' }
@@ -325,7 +325,26 @@ describe('cds.ql → cqn', () => {
})
// using CQL fragments -> uses cds.parse.expr
expect((cqn = CQL`SELECT from Foo where ID=11 and x in ( foo, 'bar', 3)`)).to.eql({
const is_v2 = !!cds.parse.expr('(1,2)').list
if (is_v2) expect((cqn = CQL`SELECT from Foo where ID=11 and x in ( foo, 'bar', 3)`)).to.eql({
SELECT: {
from: { ref: ['Foo'] },
where: [
{ ref: ['ID'] },
'=',
{ val: ID },
'and',
{ ref: ['x'] },
'in',
{list:[
{ ref: ['foo'] },
{ val: 'bar' },
{ val: 3 },
]}
],
},
})
else expect((cqn = CQL`SELECT from Foo where ID=11 and x in ( foo, 'bar', 3)`)).to.eql({
SELECT: {
from: { ref: ['Foo'] },
where: [

View File

@@ -1,7 +1,7 @@
const cds = require('@sap/cds/lib')
const { expect } = cds.test (
const { expect } = require('../test') .run (
'serve', 'AdminService', '--from', '@capire/bookshop,@capire/common', '--in-memory'
).in(__dirname)
)
const cds = require('@sap/cds/lib')
describe('Consuming Services locally', () => {
//

View File

@@ -1,5 +1,7 @@
const cds = require('@sap/cds/lib'); cds.User = cds.User.Privileged // skip auth
const { GET, POST, expect } = cds.test('bookshop').in(__dirname,'..')
const { GET, POST, expect } = require('../test') .run ('bookshop')
const cds = require('@sap/cds/lib')
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
describe('Custom Handlers', () => {

View File

@@ -1,5 +1,4 @@
const cds = require('@sap/cds/lib')
const { GET, expect } = cds.test('serve','hello/world.cds').in(__dirname,'..')
const { GET, expect } = require('../test') .run ('serve','hello/world.cds')
describe('Hello world!', () => {

View File

@@ -1,6 +1,5 @@
const cwd = process.cwd(); process.chdir (__dirname) //> only for internal CI/CD@SAP
const {expect} = require('../test')
const cds = require('@sap/cds/lib')
const {expect} = cds.test
// monkey patching older releases:
if (!cds.compile.cdl) cds.compile.cdl = cds.parse
@@ -25,8 +24,6 @@ describe('Hierarchical Data', ()=>{
expect (cds.db.model) .to.exist
})
after(()=> process.chdir(cwd))
it ('supports deeply nested inserts', ()=> INSERT.into (Cats,
{ ID:100, name:'Some Cats...', children:[
{ ID:101, name:'Cat', children:[

6
test/index.js Normal file
View File

@@ -0,0 +1,6 @@
const test = require('@sap/cds/lib/utils/tests').in(__dirname,'..')
module.exports = Object.assign(test,{run:test})
// REVISIT: With upcoming release of @sap/cds this should become:
// module.exports = require('@sap/cds/tests').in(__dirname,'..')

View File

@@ -1,5 +1,7 @@
const cds = require('@sap/cds/lib'); cds.User = cds.User.Privileged // skip auth
const { GET, expect } = cds.test ('serve', __dirname+'/localized-data.cds', '--in-memory')
const { GET, expect } = require('../test') .run ('serve', 'test/localized-data.cds', '--in-memory')
const cds = require('@sap/cds/lib')
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
describe('Localized Data', () => {

View File

@@ -1,13 +1,11 @@
const { expect } = require('../test')
const cds = require('@sap/cds/lib')
const cwd = process.cwd(); process.chdir (__dirname) //> only for internal CI/CD@SAP
const {expect} = cds.test
const _model = '@capire/reviews'
cds.User = cds.User.Privileged // hard core monkey patch
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
describe('Messaging', ()=>{
after(()=> process.chdir(cwd))
it ('should bootstrap sqlite in-memory db', async()=>{
const db = await cds.deploy (_model) .to ('sqlite::memory:')
await db.delete('Reviews')
@@ -44,16 +42,16 @@ describe('Messaging', ()=>{
// { ID: 111 + (++N), subject: "201", title: "Captivating", rating: N },
// ),
srv.create ('Reviews') .entries (
{ ID: 111 + (++N), subject: "201", title: "Captivating", rating: N }
{ ID: String(111 + (++N)), subject: "201", title: "Captivating", rating: N }
),
srv.create ('Reviews') .entries (
{ ID: 111 + (++N), subject: "201", title: "Captivating", rating: N }
{ ID: String(111 + (++N)), subject: "201", title: "Captivating", rating: N }
),
srv.create ('Reviews') .entries (
{ ID: 111 + (++N), subject: "201", title: "Captivating", rating: N }
{ ID: String(111 + (++N)), subject: "201", title: "Captivating", rating: N }
),
srv.create ('Reviews') .entries (
{ ID: 111 + (++N), subject: "201", title: "Captivating", rating: N }
{ ID: String(111 + (++N)), subject: "201", title: "Captivating", rating: N }
),
]))

View File

@@ -1,8 +1,11 @@
const cds = require('@sap/cds/lib'); cds.User = cds.User.Privileged // skip auth
const { GET, expect } = cds.test('bookshop').in(__dirname,'..')
const { GET, expect } = require('../test') .run ('bookshop')
const cds = require('@sap/cds/lib')
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
describe('OData Protocol', () => {
it('serves $metadata documents in v4', async () => {
const { headers, status, data } = await GET `/browse/$metadata`
expect(status).to.equal(200)