Compare commits

..

147 Commits
odata ... dkom

Author SHA1 Message Date
D065023
a2febd57db Merge branch 'dkom' of https://github.com/SAP-samples/cloud-cap-samples into dkom 2020-02-10 18:05:44 +01:00
D065023
5073af37d3 change folder 2020-02-10 18:05:34 +01:00
Dr. David Kunz
e8cda443ba Update README.md 2020-02-10 13:43:01 +01:00
D065023
da63d6c287 Merge branch 'dkom' of https://github.com/SAP-samples/cloud-cap-samples into dkom 2020-02-07 18:41:56 +01:00
Dr. David Kunz
009dfc53c5 Update README.md 2020-02-05 16:09:49 +01:00
Dr. David Kunz
d760516e3e Update README.md 2020-02-05 16:09:29 +01:00
Dr. David Kunz
261d238738 Update README.md 2020-02-04 09:40:53 +01:00
D065023
136eae69eb barebone 2020-01-08 18:29:28 +01:00
D065023
e0306850b4 prettier 2020-01-08 16:04:45 +01:00
D065023
b7f9b78988 prettier 2020-01-08 16:00:50 +01:00
D065023
4e5f31a29d where -> and 2020-01-08 16:00:24 +01:00
D065023
2d7efafd30 prettier 2020-01-08 16:00:07 +01:00
D065023
cf0b9c2a52 csv -> data 2020-01-08 15:28:01 +01:00
D065023
2053450404 rm tombstone 2020-01-08 13:52:23 +01:00
D065023
920600a5ff prettier 2020-01-08 13:49:59 +01:00
D065023
dc1ea91d9e prettier 2020-01-08 13:45:53 +01:00
D065023
9ba5aae999 fix bug 2020-01-08 13:43:38 +01:00
D065023
29aee15cf3 Alice doesnt need a password anymore 2020-01-07 14:47:05 +01:00
D065023
2ca5a79c7e renaming ext service 2020-01-07 12:47:30 +01:00
Dr. David Kunz
857b28aad0 Update README.md 2020-01-07 12:37:56 +01:00
Dr. David Kunz
91d1a9bcb2 Update README.md 2020-01-07 12:36:26 +01:00
Daniel
a33dbff74e restored master .cds for db/schema and cat-service 2020-01-04 07:33:35 +01:00
Daniel
7f65729dc9 Merge branch 'master' into dkom 2020-01-04 07:28:09 +01:00
Daniel
3c4aa7427e Merge branch 'master' into dkom 2020-01-03 03:10:26 +01:00
D065023
28859993ba rm file 2020-01-02 09:20:50 +01:00
D065023
e503f157f6 cosmetics 2019-12-30 14:20:11 +01:00
D065023
4eebfb5df4 cosmetics 2019-12-30 14:12:51 +01:00
D065023
28362cc835 cosmetics 2019-12-30 14:12:14 +01:00
D065023
78e6138718 minor 2019-12-30 14:09:57 +01:00
D065023
7db2c6e781 tombstone logic 2019-12-30 14:06:41 +01:00
D065023
afc5be2610 comment 2019-12-30 13:55:06 +01:00
D065023
5bc8d4dde0 cosmetics 2019-12-30 13:54:20 +01:00
D065023
60563dc816 cosmetics 2019-12-30 13:51:02 +01:00
D065023
4a1887f424 translations 2019-12-27 12:01:20 +01:00
D065023
c28287f9e4 typo 2019-12-27 11:01:58 +01:00
D065023
8cadac0051 cleanup readme 2019-12-27 10:25:35 +01:00
D065023
034c3b84f7 adjusted 2019-12-26 14:18:35 +01:00
D065023
b155754a72 adjusted 2019-12-26 14:16:59 +01:00
D065023
782e8a6696 adjusted README 2019-12-26 14:16:18 +01:00
D065023
f3c14a0625 cds -> latest 2019-12-23 18:06:04 +01:00
D065023
4cffa85079 rm todo 2019-12-23 17:26:40 +01:00
D065023
837e6bf1c8 minor 2019-12-23 17:23:00 +01:00
D065023
b394dbd234 rm event modelling 2019-12-23 17:19:53 +01:00
D065023
fdd0a256c4 made async api work 2019-12-23 15:54:52 +01:00
D065023
5b5d9da82e added eventing 2019-12-23 11:53:24 +01:00
D065023
2af54a520c Added README 2019-12-23 10:59:44 +01:00
D065023
128213aba7 rm comment 2019-12-23 10:14:14 +01:00
D065023
4bc2257cea cleanup 2019-12-23 10:12:00 +01:00
D065023
e16a343ce3 prettier 2019-12-23 10:10:03 +01:00
D065023
1f01bdf202 req.user.id === 'anonymous' in case of not logged in 2019-12-23 10:08:42 +01:00
Daniel
45843ab7bd cleaned up 2019-12-21 18:01:58 +01:00
D065023
421cea9f2b require utils 2019-12-20 16:12:29 +01:00
D065023
aa919f9d62 async conntext 2019-12-20 16:11:10 +01:00
D065023
ed814a1f75 hack for app studio 2019-12-20 16:09:11 +01:00
Daniel
c04c93cca6 c2 2019-12-20 13:37:50 +01:00
Daniel
2cde812edd cleaned up and working end to end now 2019-12-20 13:37:31 +01:00
Daniel
2f576dbb1b gaaaanz schmaler korridor 2019-12-19 18:23:38 +01:00
Daniel
7f7cd43bff adding test for GET Orders 2019-12-19 18:23:16 +01:00
Daniel
294f9feb36 added launch conf for bookshop 2019-12-19 18:22:54 +01:00
Daniel
2ebfcd8871 fixed: .contact 2019-12-19 18:22:31 +01:00
Daniel
963d0fbb6c constraint violation 2019-12-19 17:09:29 +01:00
Daniel
eebdd74bfe more work-arounds 2019-12-19 17:04:32 +01:00
Daniel
37810c9027 quick fix for empty value help 2019-12-19 16:55:02 +01:00
Daniel
48a086e9a1 quick fix for empty valuehelp 2019-12-19 16:54:21 +01:00
Daniel
659c347c71 .BusinessPartner -> .contact 2019-12-19 16:48:18 +01:00
Daniel
0bbb8e3d3b + auth conf --> we need to remove that later 2019-12-19 16:39:43 +01:00
Daniel
b3abcbcaae cleanup 2019-12-19 16:26:17 +01:00
Daniel
3716d4d5e3 fixed: auto-exposed addresses were missing 2019-12-19 16:22:42 +01:00
Daniel
226094e85c doesn' work with hana 2019-12-19 15:26:03 +01:00
Daniel
de149ea9b3 Merge branch 'D-kom-Demo' into dkom 2019-12-17 14:35:34 +01:00
Daniel
b6c1610817 updates parser version 2019-12-17 14:24:04 +01:00
Daniel
3df0981992 BUPA -> contact 2019-12-17 13:01:35 +01:00
Daniel
8c8c5f3f9d some more cosmetics tweaks 2019-12-17 12:55:09 +01:00
Daniel
2c0f69a161 some more cleanup 2019-12-17 12:46:10 +01:00
Daniel
54d0c8b35d simplified models and impl -> requires latest snapshots 2019-12-17 12:30:13 +01:00
D065023
3027a7a1e5 hack for app studio 2019-12-17 10:03:17 +01:00
David-Kunz
8eaf34f5d3 Update package.json 2019-12-17 09:59:11 +01:00
David-Kunz
0b0a22d126 Update schema.cds 2019-12-17 09:40:17 +01:00
Daniel
c0e1fb38ac cosmetics 2019-12-16 22:12:37 +01:00
D065023
74c155ca62 better 2019-12-16 21:56:32 +01:00
D065023
db16577235 no more bla 2019-12-16 21:55:33 +01:00
Daniel
53989cf609 Merge branch 'D-kom-Demo' into dkom 2019-12-16 21:54:49 +01:00
D065023
d678b51320 revert prefix 2019-12-16 21:52:43 +01:00
Daniel
5720d73b76 revised 2019-12-16 21:50:23 +01:00
D065023
06a6ac2201 fixed bug 2019-12-16 21:14:56 +01:00
D065023
125edc34e2 fixed 2019-12-16 21:12:03 +01:00
D065023
dc8e8c55df fix bug 2019-12-16 21:05:28 +01:00
D065023
f89acc00dd fix typo 2019-12-16 21:04:04 +01:00
D065023
3e725bcc26 refactor 2019-12-16 21:02:57 +01:00
D065023
dfea19334d use prefix 2019-12-16 20:22:12 +01:00
D065023
8f11de5430 cosmetics 2019-12-16 19:18:21 +01:00
D065023
38ce94d5cd syntax cleanup 2019-12-16 18:42:59 +01:00
D065023
ffe633a493 cleanup 2019-12-16 11:31:29 +01:00
D065023
e081182a7c cleanup 2019-12-16 11:30:42 +01:00
D065023
e4f8f13dbf typo 2019-12-16 11:28:06 +01:00
D065023
cc698ec23f rename ql -> query 2019-12-16 11:13:34 +01:00
D065023
382a4c562d prettier 2019-12-16 11:11:45 +01:00
D065023
811694cdf1 revert 2019-12-16 11:11:25 +01:00
D065023
83653bd095 Merge branch 'D-kom-Demo' of https://github.com/SAP-samples/cloud-cap-samples into D-kom-Demo 2019-12-16 10:29:44 +01:00
D065023
e27275d29a go to own database when remote system unavailable 2019-12-16 10:29:36 +01:00
Daniel
b9330d7f77 Merge branch 'master' into D-kom-Demo 2019-12-16 08:41:04 +01:00
D065023
f413b45e24 more checks 2019-12-13 14:26:22 +01:00
D065023
4a21b9edc3 cleaner 2019-12-13 14:20:04 +01:00
D065023
4bfd4430e1 rm comment 2019-12-13 10:42:18 +01:00
D065023
0bb3144aea rm console.logs 2019-12-13 10:41:11 +01:00
D065023
c1d2c4caef translations and cleanup 2019-12-13 09:00:56 +01:00
D065023
59f68c0f28 code reduction 2019-12-13 08:47:36 +01:00
D065023
5ba3458b27 added limit 2019-12-13 08:46:09 +01:00
D065023
199b2c8045 it works 2019-12-12 17:33:21 +01:00
Elena Oresharova
3b4abf5600 Fix side effects for address 2019-12-12 11:12:01 +01:00
D065023
f69c0ae190 fiori3 theme 2019-12-12 10:19:44 +01:00
D065023
f48cd1cc2f cosmetics 2019-12-12 10:14:23 +01:00
D065023
6bded9df98 more test data 2019-12-12 10:05:15 +01:00
D065023
5a659774b5 use msg as transaction box 2019-12-12 09:53:46 +01:00
D065023
17d6dc8cf8 rm delete 2019-12-12 08:47:50 +01:00
D065023
1a1686e340 cleanup 2019-12-11 16:38:38 +01:00
D065023
b298c9b708 fixed a bug 2019-12-11 16:37:51 +01:00
D065023
348a7b191e format 2019-12-11 13:26:22 +01:00
D065023
f56d4fe093 readonly 2019-12-11 13:22:00 +01:00
D065023
02942f5e1a nothing 2019-12-11 13:20:45 +01:00
D065023
1aa9237d20 needed to use several transactions 2019-12-11 13:19:50 +01:00
D065023
7a760cfaf8 cleanup 2019-12-11 12:52:06 +01:00
D065023
6c0d8fa444 bug fix 2019-12-11 12:49:26 +01:00
D065023
3b06003328 fixed bugs - do not change key names 2019-12-11 11:46:55 +01:00
D065023
e686b1819b fixed bugs 2019-12-11 11:46:44 +01:00
D065023
d36c2a97fa bug fix 2019-12-11 11:24:05 +01:00
D065023
f4e119342b bug fix 2019-12-11 11:19:21 +01:00
D065023
ddd02b52f2 eventing, renaming, subselecting 2019-12-11 11:12:57 +01:00
D065023
e6d5183cce impl msg mock 2019-12-06 14:35:02 +01:00
D065023
a191ecf88d rm toUpperCase for user id 2019-12-06 14:11:21 +01:00
D065023
140db39cd4 include authorization 2019-12-06 11:32:50 +01:00
D065023
68ee29598a translations 2019-12-06 11:03:59 +01:00
D065023
7deae997bb fixed minor issues 2019-12-06 10:59:50 +01:00
D065023
12aee3e38c fixed keys 2019-12-06 10:54:45 +01:00
Elena Oresharova
dfe876e2cf Fix 'City Name' label for the Address 2019-12-03 16:17:20 +01:00
Elena Oresharova
9e2c7a0974 Add side effect of filling the fields of an address once we have a selection 2019-12-03 13:54:02 +01:00
Elena Oresharova
30b2854fac Merge branch 'D-kom-Demo' of https://github.com/SAP-samples/cloud-cap-samples into D-kom-Demo 2019-12-03 13:37:07 +01:00
Elena Oresharova
4ca7e425ec Add additional columns to the value list for Addresses 2019-12-03 13:32:50 +01:00
D065023
28a51f4837 Added Country and Postal Code 2019-12-03 10:58:53 +01:00
D065023
692a360065 minor 2019-12-02 12:01:38 +01:00
D065023
b3d9fdb8b3 refactor 2019-12-02 10:57:52 +01:00
D065023
e9d10986ff rm console.log 2019-12-02 10:35:53 +01:00
D065023
7191f61806 User mapping and refactoring 2019-12-02 10:35:22 +01:00
D065023
e688e7ecee value help 2019-11-30 14:39:00 +01:00
D065023
4a2139a5f2 Address lookup to mocked S/4HANA system
What's working:
- Address information on OP
- Reading of S/4HANA Addresses
- Translations
2019-11-30 14:17:17 +01:00
D065023
ed3ecd502f async style 2019-11-29 12:24:22 +01:00
D065023
8f3d112558 prettier 2019-11-29 12:11:29 +01:00
190 changed files with 8203 additions and 5969 deletions

View File

@@ -1,11 +1,9 @@
{
"extends": "eslint:recommended",
"env": {
"browser": true,
"node": true,
"es6": true,
"jest": true,
"mocha": true
"jest": true
},
"parserOptions": {
"ecmaVersion": 2018
@@ -21,7 +19,6 @@
},
"rules": {
"no-console": "off",
"require-atomic-updates": "off",
"require-await":"warn"
"require-atomic-updates": "off"
}
}

View File

@@ -1,10 +0,0 @@
---
name: This channel is CLOSED.
about: Use our community at https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce
title: ''
labels: ''
assignees: ''
---
Please use our community on https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce

View File

@@ -1,28 +0,0 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test

2
.gitignore vendored
View File

@@ -13,5 +13,3 @@ target/
connection.properties
default-env.json
packages/messageBox
reviews/msg-box
reviews/db/test.db

View File

@@ -1,71 +0,0 @@
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
app.use('/-/:tarball', (req,res,next) => {
const url = decodeURIComponent(req.url)
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)

View File

@@ -1,29 +0,0 @@
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
projects 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

View File

@@ -1,20 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"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"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
]
}

37
.vscode/launch.json vendored
View File

@@ -4,36 +4,33 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach by Process ID",
"processId": "${command:PickProcess}",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"name": "bookshop",
"command": "cds watch bookshop",
"request": "launch",
"type": "node-terminal",
"name": "bookshop", "request": "launch", "type": "node", "runtimeExecutable": "npx", "runtimeArgs": [ "-n" ],
"args": [ "--", "cds", "run", "--in-memory" ],
"cwd": "${workspaceFolder}/packages/bookshop",
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"]
},
{
"name": "Fiori app",
"command": "cds watch fiori",
"request": "launch",
"type": "node-terminal",
"name": "cds run ...", "request": "launch", "type": "node", "runtimeExecutable": "npx", "runtimeArgs": [ "-n" ],
"args": [ "--", "cds", "run", "--with-mocks", "--in-memory?" ],
"cwd": "${workspaceFolder}/packages/${input:service}",
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"]
}
],
"inputs": [
{
"type": "pickString",
"id": "sample",
"description": "Which sample do you want to start?",
"options": ["bookshop", "fiori", "reviews", "reviews/test/bookshop"],
"id": "service",
"description": "Which service do you want to start?",
"options": [
"bookshop",
"bookstore",
"media-server",
"office-supplies",
"reviews-service"
],
"default": "bookshop"
}
]

28
.vscode/tasks.json vendored
View File

@@ -1,15 +1,17 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "jest",
"group": {
"kind": "test",
"isDefault": true
}
}
]
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "npm", "script": "watch", "path": "packages/bookshop/",
"options": { "env": { "PORT": "4004" }},
"presentation": { "group": "A" }
},
{
"type": "npm", "script": "watch", "path": "packages/reviews-service/",
"options": { "env": { "PORT": "5005" }},
"presentation": { "group": "A" }
}
]
}

370
LICENSE
View File

@@ -1,208 +1,190 @@
Apache License
SAP SAMPLE CODE LICENSE AGREEMENT
Version 2.0, January 2004
Please scroll down and read the following SAP Sample Code License Agreement
carefully ("Agreement"). By downloading, installing, or otherwise using the
SAP sample code or any materials that accompany the sample code documentation
(collectively, the "Sample Code"), You agree that this Agreement forms a legally
binding agreement between You ("You" or "Your") and SAP SE, for and on behalf
of itself and its subsidiaries and affiliates (as defined in Section 15 of the
German Stock Corporation Act), and You agree to be bound by all of the terms
and conditions stated in this Agreement. If You are trying to access or download
the Sample Code on behalf of Your employer or as a consultant or agent of a
third party (either "Your Company"), You represent and warrant that You have
the authority to act on behalf of and bind Your Company to the terms of this
Agreement and everywhere in this Agreement that refers to 'You' or 'Your' shall
also include Your Company. If You do not agree to these terms, do not attempt
to access or use the Sample Code.
http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION,
AND DISTRIBUTION
1. LICENSE: Subject to the terms of this Agreement, SAP grants You a nonexclusive,
non-transferable, non-sublicensable, revocable, royalty-free,
limited license to use, copy, and modify the Sample Code solely for Your internal
business purposes.
1. Definitions.
2. RESTRICTIONS: You must not use the Sample Code to: (a) impair, degrade or
reduce the performance or security of any SAP products, services or related
technology (collectively, "SAP Products"); (b) enable the bypassing or
circumventing of SAP's license restrictions and/or provide users with access to
the SAP Products to which such users are not licensed; or (c) permit mass data
extraction from an SAP Product to a non-SAP Product, including use,
modification, saving or other processing of such data in the non-SAP Product.
Further, You must not: (i) provide or make the Sample Code available to any
third party other than your authorized employees, contractors and agents
(collectively, “Representatives”) and solely to be used by Your Representatives
for Your own internal business purposes; ii) remove or modify any marks or
proprietary notices from the Sample Code; iii) assign this Agreement, or any
interest therein, to any third party; (iv) use any SAP name, trademark or logo
without the prior written authorization of SAP; or (v) use the Sample Code to
modify an SAP Product or decompile, disassemble or reverse engineer an SAP
Product (except to the extent permitted by applicable law). You are responsible
for any breach of the terms of this Agreement by You or Your Representatives.
3. INTELLECTUAL PROPERTY: SAP or its licensors retain all ownership and
intellectual property rights in and to the Sample Code and SAP Products. In
exchange for the right to use, copy and modify the Sample Code provided under
this Agreement, You covenant not to assert any intellectual property rights in
or to any of Your products, services, or related technology that are based on
or incorporate the Sample Code against any individual or entity in respect of
any current or future SAP Products.
4. SAP AND THIRD PARTY APIS: The Sample Code may include API (application
programming interface) calls to SAP and third-party products or services. The
access or use of the third-party products and services to which the API calls
are directed may be subject to additional terms and conditions between you and
SAP or such third parties. You (and not SAP) are solely responsible for
understanding and complying with any additional terms and conditions that apply
to the access or use of those APIs and/or third-party products and services.
SAP does not grant You any rights in or to these APIs, products or services
under this Agreement.
"License" shall mean the terms and conditions for use, reproduction, and distribution
as defined by Sections 1 through 9 of this document.
5. FREE AND OPEN SOURCE COMPONENTS: The Sample Code may include third party
free or open source components ("FOSS Components"). You may have additional
rights in such FOSS Components that are provided by the third party licensors
of those components.
6. THIRD PARTY DEPENDENCIES: The Sample Code may require third party software
dependencies ("Dependencies") for the use or operation of the Sample Code. These
Dependencies may be identified by SAP in Maven POM files, documentation or by
other means. SAP does not grant You any rights in or to such Dependencies under
this Agreement. You are solely responsible for the acquisition, installation
and use of such Dependencies.
7. WARRANTY:
a) If You are located outside the US or Canada: AS THE SAMPLE CODE IS PROVIDED
TO YOU FREE OF CHARGE, SAP DOES NOT GUARANTEE OR WARRANT ANY FEATURES OR
QUALITIES OF THE SAMPLE CODE OR GIVE ANY UNDERTAKING WITH REGARD TO ANY OTHER
QUALITY. NO SUCH WARRANTY OR UNDERTAKING SHALL BE IMPLIED BY YOU FROM ANY
DESCRIPTION IN THE SAMPLE CODE OR ANY OTHER MATERIALS, COMMUNICATION OR
ADVERTISEMENT. IN PARTICULAR, SAP DOES NOT WARRANT THAT THE SAMPLE CODE WILL BE
AVAILABLE UNINTERRUPTED, ERROR FREE, OR PERMANENTLY AVAILABLE. ALL WARRANTY
CLAIMS RESPECTING THE SAMPLE CODE ARE SUBJECT TO THE LIMITATION OF LIABILITY
STIPULATED IN SECTION 8 BELOW.
b) If You are located in the US or Canada: THE SAMPLE CODE IS LICENSED TO YOU
"AS IS", WITHOUT ANY WARRANTY, ESCROW, TRAINING, MAINTENANCE, OR SERVICE
OBLIGATIONS WHATSOEVER ON THE PART OF SAP. SAP MAKES NO EXPRESS OR IMPLIED
WARRANTIES OR CONDITIONS OF SALE OF ANY TYPE WHATSOEVER, INCLUDING BUT NOT
LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY AND OF FITNESS FOR A PARTICULAR
PURPOSE. IN PARTICULAR, SAP DOES NOT WARRANT THAT THE SAMPLE CODE WILL BE
AVAILABLE UNINTERRUPTED, ERROR FREE, OR PERMANENTLY AVAILABLE. YOU ASSUME ALL
RISKS ASSOCIATED WITH THE USE OF THE SAMPLE CODE, INCLUDING WITHOUT LIMITATION
RISKS RELATING TO QUALITY, AVAILABILITY, PERFORMANCE, DATA LOSS, AND UTILITY IN
A PRODUCTION ENVIRONMENT.
c) For all locations: SAP DOES NOT MAKE ANY REPRESENTATIONS OR WARRANTIES IN
RESPECT OF THIRD PARTY DEPENDENCIES, APIS, PRODUCTS AND SERVICES, INCLUDING BUT
NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY AND OF FITNESS FOR A
PARTICULAR PURPOSE. IN PARTICULAR, SAP DOES NOT WARRANT THAT THIRDPARTY
DEPENDENCIES, APIS, PRODUCTS AND SERVICES WILL BE AVAILABLE, ERROR FREE,
INTEROPERABLE WITH THE SAMPLE CODE, SUITABLE FOR ANY PARTICULAR PURPOSE OR NONINFRINGING.
YOU ASSUME ALL RISKS ASSOCIATED WITH THE USE OF THIRD
PARTY DEPENDENCIES, APIS, PRODUCTS AND SERVICES, INCLUDING WITHOUT LIMITATION
RISKS RELATING TO QUALITY, AVAILABILITY, PERFORMANCE, DATA LOSS, UTILITY IN A
PRODUCTION ENVIRONMENT, AND NON-INFRINGEMENT. IN NO EVENT WILL SAP BE LIABLE
DIRECTLY OR INDIRECTLY IN RESPECT OF ANY USE OF THIRD PARTY DEPENDENCIES, APIS,
PRODUCTS AND SERVICES BY YOU.
8. LIMITATION OF LIABILITY:
a) If You are located outside the US or Canada: IRRESPECTIVE OF THE LEGAL
REASONS, SAP SHALL ONLY BE LIABLE FOR DAMAGES UNDER THIS AGREEMENT IF SUCH
DAMAGE (I) CAN BE CLAIMED UNDER THE GERMAN PRODUCT LIABILITY ACT OR (II) IS
CAUSED BY INTENTIONAL MISCONDUCT OF SAP OR (III) CONSISTS OF PERSONAL INJURY.
IN ALL OTHER CASES, NEITHER SAP NOR ITS EMPLOYEES, AGENTS AND SUBCONTRACTORS
SHALL BE LIABLE FOR ANY KIND OF DAMAGE OR CLAIMS HEREUNDER.
b) If You are located in the US or Canada: IN NO EVENT SHALL SAP BE LIABLE TO
YOU, YOUR COMPANY OR TO ANY THIRD PARTY FOR ANY DAMAGES IN AN AMOUNT IN EXCESS
OF $100 ARISING IN CONNECTION WITH YOUR USE OF OR INABILITY TO USE THE SAMPLE
CODE OR IN CONNECTION WITH SAP'S PROVISION OF OR FAILURE TO PROVIDE SERVICES
PERTAINING TO THE SAMPLE CODE, OR AS A RESULT OF ANY DEFECT IN THE SAMPLE COED.
THIS DISCLAIMER OF LIABILITY SHALL APPLY REGARDLESS OF THE FORM OF ACTION THAT
MAY BE BROUGHT AGAINST SAP, WHETHER IN CONTRACT OR TORT, INCLUDING WITHOUT
LIMITATION ANY ACTION FOR NEGLIGENCE. YOUR SOLE REMEDY IN THE EVENT OF BREACH
OF THIS AGREEMENT BY SAP OR FOR ANY OTHER CLAIM RELATED TO THE SAMPLE CODE SHALL
BE TERMINATION OF THIS AGREEMENT. NOTWITHSTANDING ANYTHING TO THE CONTRARY
HEREIN, UNDER NO CIRCUMSTANCES SHALL SAP OR ITS LICENSORS BE LIABLE TO YOU OR
ANY OTHER PERSON OR ENTITY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, OR
INDIRECT DAMAGES, LOSS OF GOOD WILL OR BUSINESS PROFITS, WORK STOPPAGE, DATA
LOSS, COMPUTER FAILURE OR MALFUNCTION, ANY AND ALL OTHER COMMERCIAL DAMAGES OR
LOSS, OR EXEMPLARY OR PUNITIVE DAMAGES.
9. INDEMNITY: You will fully indemnify, hold harmless and defend SAP against
law suits based on any claim: (a) that any of Your products, services or related
technology that are based on or incorporate the Sample Code infringes or
misappropriates any patent, copyright, trademark, trade secrets, or other
proprietary rights of a third party, or (b) related to Your alleged violation
of the terms of this Agreement.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
10. EXPORT: The Sample Code is subject to German, EU and US export control
regulations. You confirm that: a) You will not use the Sample Code for, and
will not allow the Sample Code to be used for, any purposes prohibited by
German, EU and US law, including, without limitation, for the development,
design, manufacture or production of nuclear, chemical or biological weapons of
mass destruction; b) You are not located in Cuba, Iran, Sudan, Iraq, North
Korea, Syria, nor any other country to which the United States has prohibited
export or that has been designated by the U.S. Government as a "terrorist
supporting" country (any, an "US Embargoed Country"); c) You are not a citizen,
national or resident of, and are not under the control of, a US Embargoed
Country; d) You will not download or otherwise export or re-export the Sample
Code, directly or indirectly, to a US Embargoed Country nor to citizens,
nationals or residents of a US Embargoed Country; e) You are not listed on the
United States Department of Treasury lists of Specially Designated Nationals,
Specially Designated Terrorists, and Specially Designated Narcotic Traffickers,
nor listed on the United States Department of Commerce Table of Denial Orders
or any other U.S. government list of prohibited or restricted parties and f)
You will not download or otherwise export or re-export the Sample Code, directly
or indirectly, to persons on the above-mentioned lists.
11. SUPPORT: SAP does not offer support for the Sample Code.
12. TERM AND TERMINATION: You may terminate this Agreement by destroying all
copies of the Sample Code in Your possession or control. SAP may terminate Your
license to use the Sample Code immediately if You fail to comply with any of
the terms of this Agreement, or, for SAP's convenience by providing you with
ten (10) days written notice of termination. In case of termination or
expiration of this Agreement, You must immediately destroy all copies of the
Sample Code in your possession or control. In the event Your Company is acquired
(by merger, purchase of stock, assets or intellectual property or exclusive
license), or You become employed, by a direct competitor of SAP, then this
Agreement and all licenses granted to You in this Agreement shall immediately
terminate upon the date of such acquisition or change of employment.
"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, "control" means (i) the power, direct
or indirect, to cause the 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.
13. LAW/VENUE:
a) If You are located outside the US or Canada: This Agreement is governed by
and construed in accordance with the laws of Germany without reference to its
conflicts of law principles. You and SAP agree to submit to the exclusive
jurisdiction of, and venue in, the courts located in Karlsruhe, Germany in any
dispute arising out of or relating to this Agreement or the Sample Code. The
United Nations Convention on Contracts for the International Sale of Goods shall
not apply to this Agreement.
b) If You are located in the US or Canada: This Agreement shall be governed by
and construed in accordance with the laws of the State of New York, USA without
reference to its conflicts of law principles. You and SAP agree to submit to
the exclusive jurisdiction of, and venue in, the courts located in New York,
New York, USA in any dispute arising out of or relating to this Agreement or
the Sample Code. The United Nations Convention on Contracts for the
International Sale of Goods shall not apply to this Agreement.
14. MISCELLANEOUS: This Agreement is the complete agreement between the parties
respecting the Sample Code. This Agreement supersedes all prior or
contemporaneous agreements or representations with regards to the Sample Code.
If any term of this Agreement is found to be invalid or unenforceable, the
surviving provisions shall remain effective. SAP's failure to enforce any right
or provisions stipulated in this Agreement will not constitute a waiver of such
provision, or any other provision of this Agreement.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions
granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation
or translation of a Source form, including but not limited to compiled object
code, generated documentation, 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 copyright notice that
is included in or attached to the work (an example is provided in the Appendix
below).
"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 represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative
Works shall not include works that remain 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 the original version
of the Work and any modifications or additions to that Work or Derivative
Works thereof, that is intentionally submitted to Licensor for inclusion in
the Work by the copyright owner 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
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor
for the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"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.
2. Grant of Copyright License. Subject to the terms and conditions of this
License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable copyright license to reproduce, prepare
Derivative Works of, publicly display, publicly perform, sublicense, and distribute
the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License,
each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (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 Contribution(s)
alone or by combination of their Contribution(s) with the Work to which such
Contribution(s) was submitted. If You institute patent litigation against
any entity (including a cross-claim or counterclaim in a lawsuit) alleging
that the Work or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses granted to You
under this License for that Work shall terminate as of the date such litigation
is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or
Derivative Works thereof in any medium, with or without modifications, and
in Source or Object form, provided that You meet the following conditions:
(a) You must give any other recipients of the Work or Derivative Works a copy
of this License; and
(b) You must cause any modified files to carry prominent notices stating that
You changed the files; and
(c) You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source
form of the Work, excluding those notices that do not pertain to any part
of the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its distribution,
then any Derivative Works that You distribute must include a readable copy
of the attribution notices contained within such NOTICE file, excluding those
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
of the Derivative Works; within the Source form or documentation, if provided
along with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works
that You distribute, alongside or as an addendum to the NOTICE text from the
Work, provided that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction,
or distribution of Your modifications, or for any such Derivative Works as
a whole, provided Your use, reproduction, and distribution of the Work otherwise
complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any
Contribution intentionally submitted for inclusion in the Work by You to the
Licensor shall be under the terms and conditions of this License, without
any additional terms or conditions. Notwithstanding the above, nothing herein
shall supersede or modify the terms of any separate license agreement you
may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names,
trademarks, service marks, or product names of the Licensor, except as required
for reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to
in writing, Licensor provides the Work (and each Contributor provides its
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied, including, without limitation, any warranties
or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR
A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness
of using or redistributing the Work and assume any risks associated with Your
exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether
in tort (including negligence), contract, or otherwise, unless required by
applicable law (such as deliberate and grossly negligent acts) or agreed to
in writing, shall any Contributor be liable to You for damages, including
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
to use the Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all other commercial
damages or losses), even if such Contributor has been advised of the possibility
of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work
or Derivative Works thereof, You may choose to offer, and charge a fee for,
acceptance of support, warranty, indemnity, or other liability obligations
and/or rights consistent with this License. However, in accepting such obligations,
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,
and hold each Contributor harmless for any liability incurred by, or claims
asserted against, such Contributor by reason of your accepting any such warranty
or additional liability. END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own identifying
information. (Don't include the brackets!) The text should be enclosed in
the appropriate comment syntax for the file format. We also recommend that
a file or class name and description of purpose be included on the same "printed
page" as the copyright notice for easier identification within third-party
archives.
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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
v1.0-071618

1
NOTICE Normal file
View File

@@ -0,0 +1 @@
Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved.

106
README.md
View File

@@ -1,83 +1,73 @@
# Welcome to cap/samples
# cloud-cap-samples
Find here a collection of samples for the [SAP Cloud Application Programming Model](https://cap.cloud.sap) organized in a simplistic [monorepo setup](samples.md#all-in-one-monorepo). &rarr; See [**Overview** of contained samples](samples.md)
This is a monorepository for sample projects on [SAP Cloud Application Programming Model](https://cap.cloud.sap).
![](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)
## Description
This repository provides a list of samples and reusable packages created based on SAP Cloud Application Programming Model.
The SAP Cloud Application Programming Model enables you to quickly create business applications by allowing you to focus on your domain logic. It offers a consistent end-to-end programming model that includes languages, libraries and APIs tailored for full-stack development on SAP Cloud Platform.
The samples provided can be run in a local setup on SQLite Database.
### Preliminaries
## Requirements
* [Node.js](https://nodejs.org/en/) v8 or higher
* [Git](https://git-scm.com)
* [SQLite DB](https://www.sqlite.org/download.html) (Windows only; pre-installed on Mac/Linux)
1. Install [**@sap/cds-dk**](https://cap.cloud.sap/docs/get-started/) globally:
#### Optional (if you want to import the code into an editor)
* [VS Code](https://code.visualstudio.com)
* [Add CDS extension to VS](https://cap.cloud.sap/docs/get-started/in-vscode#add-cds-editor)
```sh
npm i -g @sap/cds-dk
```
2. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/tools#vscode)
### Download
If you have [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/master.zip).
## Download and Installation
#### Install `cds` development kit
```sh
git clone https://github.com/sap-samples/cloud-cap-samples samples
cd samples
# sets the registry for `@sap` packages
npm set @sap:registry=https://npm.sap.com
npm install -g @sap/cds-dk
cds #> test-run it
```
Got issues? Check out the [documentation](https://cap.cloud.sap/docs/get-started/).
### Setup
#### Clone and build the application
`git clone https://github.com/SAP-samples/cloud-cap-samples samples && cd samples && npm i`
In the samples folder run:
#### Run the samples
```sh
npm install
```
With that you're ready to run the samples, e.g. start the [_bookshop_](./packages/bookshop) sample as follows:
### Run
`npm run bookshop`
With that you're ready to run the samples, for example:
## Test
```sh
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
```
> While mocha is a bit smaller and faster, jest runs tests in parallel and isolation, which allows to run all tests.
For example, try these links in your browser:
- <http://localhost:4004> to test with generic index page.
- <http://localhost:4004/fiori.html> to test with Fiori sandbox.
### Serve `npm`
## Debug
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
```
For example, in [VS Code](https://code.visualstudio.com) switch to _Debug_ view and launch one of the prepared _cds run_ launch configurations.
## Get Support
## Limitations
Check out the documentation at [https://cap.cloud.sap](https://cap.cloud.sap). <br>
In case you have a question, find a bug, or otherwise need support, please use our [community](https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce).
None
## Known Issues
None
## How to obtain support
Check out the documentation on https://cap.cloud.sap. In case you find a bug, or you need additional support, please open an issue [here](https://github.com/SAP-samples/cloud-cap-samples/issues/new) in GitHub.
## To-Do (upcoming changes)
None
## License
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.
Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under SAP Sample Code License Agreement, except as noted otherwise in the [LICENSE](/LICENSE) file.

View File

@@ -1,25 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach to...",
"type": "node",
"request": "attach",
"processId": "${command:PickProcess}",
"skipFiles": ["<node_internals>/**"]
},
{
"name": "cds run",
"type": "node",
"request": "launch",
"runtimeExecutable": "npx",
"runtimeArgs": ["-n"],
"args": ["--", "cds", "run", "--with-mocks", "--in-memory?"],
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"]
}
]
}

View File

@@ -1,2 +0,0 @@
// Incorporate pre-build extensions from...
using from '@capire/common';

View File

@@ -1,48 +0,0 @@
/* global Vue axios */ //> from vue.html
const $ = sel => document.querySelector(sel)
const GET = (url) => axios.get('/browse'+url)
const POST = (cmd,data) => axios.post('/browse'+cmd,data)
const books = new Vue ({
el:'#app',
data: {
list: [],
book: undefined,
order: { amount:1, succeeded:'', failed:'' }
},
methods: {
search: ({target:{value:v}}) => books.fetch(v && '&$search='+v),
async fetch (etc='') {
const {data} = await GET(`/ListOfBooks?$expand=genre,currency${etc}`)
books.list = data.value
},
async inspect (eve) {
const book = books.book = books.list [eve.currentTarget.rowIndex-1]
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`)
Object.assign (book, res.data)
books.order = { amount:1 }
setTimeout (()=> $('form > input').focus(), 111)
},
async submitOrder () {
const {book,order} = books, amount = parseInt (order.amount) || 1 // REVISIT: Okra should be less strict
try {
const res = await POST(`/submitOrder`, { amount, book: book.ID })
book.stock = res.data.stock
books.order = { amount, succeeded: `Successfully orderd ${amount} item(s).` }
} catch (e) {
books.order = { amount, failed: e.response.data.error.message }
}
}
}
})
// initially fill list of books
books.fetch()

View File

@@ -1,65 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title> Capire Books </title>
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<style>
.hovering tr:hover td { color:cyan; background: #123; cursor: pointer; }
.rating-stars { color:teal }
.succeeded { color:teal }
.failed { color:red }
</style>
</head>
<body class="small-container", style="margin-top: 70px;">
<div id='app'>
<h1> {{ document.title }} </h1>
<input type="text" placeholder="Search..." @input="search">
<table id='books' class="hovering">
<thead>
<th> Book </th>
<th> Author </th>
<th> Genre </th>
<th> Rating </th>
<th> Price </th>
</thead>
<tr v-for="book in list" v-bind:id="book.ID" v-on:click="inspect">
<td>{{ book.title }}</td>
<td>{{ book.author }}</td>
<td>{{ book.genre.name }}</td>
<td class="rating-stars">
{{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }}
</td>
<td>{{ book.currency.symbol }} {{ book.price }}</td>
</tr>
</table>
<div v-if="book">
<img v-bind:src="book.image" alt=""/>
<label style="text-align:right">
<span class="succeeded"> {{ order.succeeded }} </span>
<span class="failed"> {{ order.failed }} </span>
&nbsp;&nbsp; {{ book.stock }} in stock
</label>
<form @submit.prevent="submitOrder" style="float:right; display:flex; flex-direction:row-reverse">
<input type="number" v-model="order.amount" v-bind:class="{ failed: order.failed }" style="width:5em">
<input type="submit" value="Order:" class="muted-button">
</form>
<h4> {{ book.title }} </h4>
<p> {{ book.descr }} </p>
</div>
<div v-else>
( click on a row to see details... )
</div>
</div>
</body>
<script src="app.js"></script>
</html>

View File

@@ -1,16 +0,0 @@
ID;parent_ID;name
10;;Fiction
11;10;Drama
12;10;Poetry
13;10;Fantasy
14;10;Science Fiction
15;10;Romance
16;10;Mystery
17;10;Thriller
18;10;Dystopia
19;10;Fairy Tale
20;;Non-Fiction
21;20;Biography
22;21;Autobiography
23;20;Essay
24;20;Speech
1 ID parent_ID name
2 10 Fiction
3 11 10 Drama
4 12 10 Poetry
5 13 10 Fantasy
6 14 10 Science Fiction
7 15 10 Romance
8 16 10 Mystery
9 17 10 Thriller
10 18 10 Dystopia
11 19 10 Fairy Tale
12 20 Non-Fiction
13 21 20 Biography
14 22 21 Autobiography
15 23 20 Essay
16 24 20 Speech

View File

@@ -1,4 +0,0 @@
namespace sap.capire.bookshop; //> important for reflection
using from './db/schema';
using from './srv/cat-service';
using from './srv/admin-service';

View File

@@ -1 +0,0 @@
exports.CatalogService = require('./srv/cat-service')

View File

@@ -1,23 +0,0 @@
{
"name": "@capire/bookshop",
"version": "1.0.0",
"description": "A simple self-contained bookshop service.",
"dependencies": {
"@capire/common": "*",
"@sap/cds": "^4",
"express": "^4.17.1",
"passport": "0.4.1"
},
"scripts": {
"genres": "cds serve test/genres.cds",
"start": "cds run",
"watch": "cds watch"
},
"cds": {
"requires": {
"db": {
"kind": "sql"
}
}
}
}

View File

@@ -1,31 +0,0 @@
# Bookshop Getting Started Sample
This stand-alone sample introduces the essential tasks in the development of CAP-based services as also covered in the [Getting Started guide in capire](https://cap.cloud.sap/docs/get-started/in-a-nutshell).
## Hypothetical Use Cases
1. Build a service that allows to browse _Books_ and _Authors_.
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).
## Running the Sample
```sh
npm run watch
```
## Content & Best Practices
| Links to capire | Sample files / folders |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| [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/guides/reuse-and-compose) | [`./index.cds`](./index.cds) |

View File

@@ -1,5 +0,0 @@
using { sap.capire.bookshop as my } from '../db/schema';
service AdminService @(requires:'admin') {
entity Books as projection on my.Books;
entity Authors as projection on my.Authors;
}

View File

@@ -1,12 +0,0 @@
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,14 +0,0 @@
using { sap.capire.bookshop as my } from '../db/schema';
service CatalogService @(path:'/browse') {
@readonly entity Books as SELECT from my.Books { *,
author.name as author
} excluding { createdBy, modifiedBy };
@readonly entity ListOfBooks as SELECT from Books
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,28 +0,0 @@
const cds = require('@sap/cds')
const { Books } = cds.entities ('sap.capire.bookshop')
class CatalogService extends cds.ApplicationService { init(){
// Reduce stock of ordered books if available stock suffices
this.on ('submitOrder', async req => {
const {book,amount} = req.data, tx = cds.tx(req)
let {stock} = await tx.read('stock').from(Books,book)
if (stock >= amount) {
await tx.update (Books,book).with ({ stock: stock -= amount })
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
this.after ('READ','Books', each => {
if (each.stock > 111) {
each.title += ` -- 11% discount!`
}
})
return super.init()
}}
module.exports = { CatalogService }

View File

@@ -1,4 +0,0 @@
using { sap.capire.bookshop as my } from '../db/schema';
service TestService {
entity Genres as projection on my.Genres;
}

View File

@@ -1,82 +0,0 @@
@server = http://localhost:4004
@me = Authorization: Basic {{$processEnv USER}}:
### ------------------------------------------------------------------------
# Get service info
GET {{server}}/browse
{{me}}
### ------------------------------------------------------------------------
# Get $metadata document
GET {{server}}/browse/$metadata
{{me}}
### ------------------------------------------------------------------------
# Browse Books as any user
GET {{server}}/browse/Books?
# &$select=title,stock
# &$expand=currency
# &sap-language=de
{{me}}
### ------------------------------------------------------------------------
# Fetch Authors as admin
GET {{server}}/admin/Authors?
# &$select=name,dateOfBirth,placeOfBirth
# &$expand=books($select=title;$expand=currency)
# &$filter=ID eq 101
# &sap-language=de
Authorization: Basic alice:
### ------------------------------------------------------------------------
# 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
# (send that three times to get out-of-stock message)
POST {{server}}/browse/submitOrder
Content-Type: application/json
{{me}}
{ "book":201, "amount":5 }
### ------------------------------------------------------------------------
# Browse Genres
GET {{server}}/browse/Genres?
# &$filter=parent_ID eq null&$select=name
# &$expand=children($select=name)
{{me}}

View File

@@ -1,5 +0,0 @@
code;name
de;German
fr;French
en;English
en_GB;British English
1 code name
2 de German
3 fr French
4 en English
5 en_GB British English

View File

@@ -1,10 +0,0 @@
code;locale;name
de;en;German
de;de;Deutsch
de;fr;Allemande
fr;en;French
fr;de;Französisch
fr;fr;Francais
en;en;English
en;de;Englisch
en;fr;Anglais
1 code locale name
2 de en German
3 de de Deutsch
4 de fr Allemande
5 fr en French
6 fr de Französisch
7 fr fr Francais
8 en en English
9 en de Englisch
10 en fr Anglais

View File

@@ -1,45 +0,0 @@
using { sap } from '@sap/cds/common';
extend sap.common.Currencies with {
// Currencies.code = ISO 4217 alphabetic three-letter code
// with the first two letters being equal to ISO 3166 alphabetic country codes
// See also:
// [1] https://www.iso.org/iso-4217-currency-codes.html
// [2] https://www.currency-iso.org/en/home/tables/table-a1.html
// [3] https://www.ibm.com/support/knowledgecenter/en/SSZLC2_7.0.0/com.ibm.commerce.payments.developer.doc/refs/rpylerl2mst97.htm
numcode : Integer;
exponent : Integer; //> e.g. 2 --> 1 Dollar = 10^2 Cent
minor : String; //> e.g. 'Cent'
}
/**
* The Code Lists below are designed as optional extensions to
* the base schema. Switch them on by adding an Association to
* one of the code list entities in your models or by:
* annotate sap.common.Countries with @cds.persistence.skip:false;
*/
context sap.common_countries {
extend sap.common.Countries {
regions : Composition of many Regions on regions._parent = $self.code;
}
entity Regions : sap.common.CodeList {
key code : String(5); // ISO 3166-2 alpha5 codes, e.g. DE-BW
children : Composition of many Regions on children._parent = $self.code;
cities : Composition of many Cities on cities.region = $self;
_parent : String(11);
}
entity Cities : sap.common.CodeList {
key code : String(11);
region : Association to Regions;
districts : Composition of many Districts on districts.city = $self;
}
entity Districts : sap.common.CodeList {
key code : String(11);
city : Association to Cities;
}
}

View File

@@ -1,8 +0,0 @@
{
"name": "@capire/common",
"description": "Provides a pre-built extension package for std @sap/cds/common",
"version": "1.0.0",
"dependencies": {
"@sap/cds": "latest"
}
}

View File

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

View File

@@ -1,25 +0,0 @@
Books = Books
Book = Book
ID = ID
Title = Title
Author = Author
AuthorID = Author ID
Stock = Stock
Name = Name
AuthorName = Author's Name
DateOfBirth = Date of Birth
DateOfDeath = Date of Death
PlaceOfBirth = Place of Birth
PlaceOfDeath = Place of Death
Authors = Authors
Order = Order
Orders = Orders
Price = Price
Genre = Genre
Genres = Genres
SubGenres = Sub Genres
NumCode = Numeric Code
MinorUnit = Minor Unit
Exponent = Exponent

View File

@@ -1,72 +0,0 @@
using AdminService from '@capire/bookshop';
////////////////////////////////////////////////////////////////////////////
//
// Books Object Page
//
annotate AdminService.Books with @(
UI: {
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>General}', Target: '@UI.FieldGroup#General'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Translations}', Target: 'texts/@UI.LineItem'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Admin}', Target: '@UI.FieldGroup#Admin'},
],
FieldGroup#General: {
Data: [
{Value: title},
{Value: author_ID},
{Value: genre_ID},
{Value: descr},
]
},
FieldGroup#Details: {
Data: [
{Value: stock},
{Value: price},
{Value: currency_code, Label: '{i18n>Currency}'},
]
},
FieldGroup#Admin: {
Data: [
{Value: createdBy},
{Value: createdAt},
{Value: modifiedBy},
{Value: modifiedAt}
]
}
}
);
////////////////////////////////////////////////////////////
//
// Draft for Localized Data
//
annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
annotate AdminService.Books with @odata.draft.enabled;
annotate AdminService.Books_texts with @(
UI: {
Identification: [{Value:title}],
SelectionFields: [ locale, title ],
LineItem: [
{Value: locale, Label: 'Locale'},
{Value: title, Label: 'Title'},
{Value: descr, Label: 'Description'},
]
}
);
// Add Value Help for Locales
annotate AdminService.Books_texts {
locale @ValueList:{entity:'Languages',type:#fixed}
}
// In addition we need to expose Languages through AdminService
using { sap } from '@sap/cds/common';
extend service AdminService {
entity Languages as projection on sap.common.Languages;
}

View File

@@ -1,8 +0,0 @@
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
"use strict";
return AppComponent.extend("admin.Component", {
metadata: { manifest: "json" }
});
});
/* eslint no-undef:0 */

View File

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

View File

@@ -1,7 +0,0 @@
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
"use strict";
return AppComponent.extend("bookshop.Component", {
metadata: { manifest: "json" }
});
});
/* eslint no-undef:0 */

View File

@@ -1,257 +0,0 @@
/*
Common Annotations shared by all apps
*/
using { sap.capire.bookshop as my } from '@capire/bookshop';
using { sap.common } from '@capire/common';
////////////////////////////////////////////////////////////////////////////
//
// Books Lists
//
annotate my.Books with @(
Common.SemanticKey: [title],
UI: {
Identification: [{Value:title}],
SelectionFields: [ ID, author_ID, price, currency_code ],
LineItem: [
{Value: ID},
{Value: title},
{Value: author.name, Label:'{i18n>Author}'},
{Value: genre.name},
{Value: stock},
{Value: price},
{Value: currency.symbol, Label:' '},
]
}
) {
author @ValueList.entity:'Authors';
};
////////////////////////////////////////////////////////////////////////////
//
// Books Details
//
annotate my.Books with @(
UI: {
HeaderInfo: {
TypeName: '{i18n>Book}',
TypeNamePlural: '{i18n>Books}',
Title: {Value: title},
Description: {Value: author.name}
},
}
);
////////////////////////////////////////////////////////////////////////////
//
// Books Elements
//
annotate my.Books with {
ID @title:'{i18n>ID}' @UI.HiddenFilter;
title @title:'{i18n>Title}';
genre @title:'{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly };
author @title:'{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
price @title:'{i18n>Price}';
stock @title:'{i18n>Stock}';
descr @UI.MultiLineText;
}
////////////////////////////////////////////////////////////////////////////
//
// Genres List
//
annotate my.Genres with @(
Common.SemanticKey: [name],
UI: {
SelectionFields: [ name ],
LineItem:[
{Value: name},
{Value: parent.name, Label: 'Main Genre'},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Genre Details
//
annotate my.Genres with @(
UI: {
Identification: [{Value:name}],
HeaderInfo: {
TypeName: '{i18n>Genre}',
TypeNamePlural: '{i18n>Genres}',
Title: {Value: name},
Description: {Value: ID}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>SubGenres}', Target: 'children/@UI.LineItem'},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Genres Elements
//
annotate my.Genres with {
ID @title: '{i18n>ID}';
name @title: '{i18n>Genre}';
}
////////////////////////////////////////////////////////////////////////////
//
// Authors List
//
annotate my.Authors with @(
Common.SemanticKey: [name],
UI: {
Identification: [{Value:name}],
SelectionFields: [ name ],
LineItem:[
{Value: ID},
{Value: name},
{Value: dateOfBirth},
{Value: dateOfDeath},
{Value: placeOfBirth},
{Value: placeOfDeath},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Author Details
//
annotate my.Authors with @(
UI: {
HeaderInfo: {
TypeName: '{i18n>Author}',
TypeNamePlural: '{i18n>Authors}',
Title: {Value: name},
Description: {Value: dateOfBirth}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Target: 'books/@UI.LineItem'},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Authors Elements
//
annotate my.Authors with {
ID @title:'{i18n>ID}' @UI.HiddenFilter;
name @title:'{i18n>Name}';
dateOfBirth @title:'{i18n>DateOfBirth}';
dateOfDeath @title:'{i18n>DateOfDeath}';
placeOfBirth @title:'{i18n>PlaceOfBirth}';
placeOfDeath @title:'{i18n>PlaceOfDeath}';
}
////////////////////////////////////////////////////////////////////////////
//
// Languages List
//
annotate common.Languages with @(
Common.SemanticKey: [code],
Identification: [{Value:code}],
UI: {
SelectionFields: [ name, descr ],
LineItem:[
{Value: code},
{Value: name},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Language Details
//
annotate common.Languages with @(
UI: {
HeaderInfo: {
TypeName: '{i18n>Language}',
TypeNamePlural: '{i18n>Languages}',
Title: {Value: name},
Description: {Value: descr}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
],
FieldGroup#Details: {
Data: [
{Value: code},
{Value: name},
{Value: descr}
]
},
}
);
////////////////////////////////////////////////////////////////////////////
//
// Currencies List
//
annotate common.Currencies with @(
Common.SemanticKey: [code],
Identification: [{Value:code}],
UI: {
SelectionFields: [ name, descr ],
LineItem:[
{Value: descr},
{Value: symbol},
{Value: code},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Currency Details
//
annotate common.Currencies with @(
UI: {
HeaderInfo: {
TypeName: '{i18n>Currency}',
TypeNamePlural: '{i18n>Currencies}',
Title: {Value: descr},
Description: {Value: code}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Extended}', Target: '@UI.FieldGroup#Extended'},
],
FieldGroup#Details: {
Data: [
{Value: name},
{Value: symbol},
{Value: code},
{Value: descr}
]
},
FieldGroup#Extended: {
Data: [
{Value: numcode},
{Value: minor},
{Value: exponent}
]
},
}
);
////////////////////////////////////////////////////////////////////////////
//
// Currencies Elements
//
annotate common.Currencies with {
numcode @title:'{i18n>NumCode}';
minor @title:'{i18n>MinorUnit}';
exponent @title:'{i18n>Exponent}';
}

View File

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

View File

@@ -1,30 +0,0 @@
{
"name": "@capire/fiori",
"version": "1.0.0",
"dependencies": {
"@capire/bookshop": "*",
"@capire/reviews": "*",
"@capire/orders": "*",
"@capire/common": "*",
"@sap/cds": "^4",
"express": "^4.17.1",
"passport": "0.4.1"
},
"scripts": {
"start": "cds run --in-memory?",
"watch": "cds watch"
},
"cds": {
"requires": {
"ReviewsService": {
"kind": "odata", "model": "@capire/reviews"
},
"OrdersService": {
"kind": "odata", "model": "@capire/orders"
},
"db": {
"kind": "sql"
}
}
}
}

View File

@@ -1,19 +0,0 @@
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))
})
cds.once('served', require('./srv/mashup'))
module.exports = cds.server

View File

@@ -1,25 +0,0 @@
////////////////////////////////////////////////////////////////////////////
//
// 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
}

View File

@@ -1,59 +0,0 @@
////////////////////////////////////////////////////////////////////////////
//
// 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

@@ -1,65 +0,0 @@
@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)

View File

@@ -1,7 +0,0 @@
{
"name": "@capire/hello-world",
"version": "1.0.0",
"scripts": {
"watch": "cds serve world.cds"
}
}

View File

@@ -1 +0,0 @@
GET http://localhost:4004/say/hello(to='world')

View File

@@ -1,3 +0,0 @@
service say {
function hello (to:String) returns String;
}

View File

@@ -1,3 +0,0 @@
module.exports = class say {
hello(req) { return `Hello ${req.data.to}!` }
}

1
lerna.json Normal file
View File

@@ -0,0 +1 @@
{"packages":["packages/*"],"version":"1.0.0"}

View File

@@ -1,13 +0,0 @@
namespace sap.capire.media;
entity Media {
key id:Integer;
@Core.MediaType: mediaType
content : LargeBinary ;
@Core.IsMediaType: true
mediaType : String;
fileName : String;
applicationName:String;
}

View File

@@ -1,2 +0,0 @@
using from './db/data-model';
using from './srv/media-service';

View File

@@ -1,19 +0,0 @@
{
"name": "@capire/media",
"version": "1.0.0",
"dependencies": {
"lokijs": "^1.5.6"
},
"files": [
"db",
"srv",
"index.cds"
],
"cds": {
"requires": {
"db": {
"kind": "sql"
}
}
}
}

View File

@@ -1,8 +0,0 @@
using { sap.capire.media as db } from '../db/data-model';
namespace sap.capire.media;
service MediaServer {
entity Media as projection on db.Media ;
}

View File

@@ -1,68 +0,0 @@
const loki = require('lokijs')
const db = new loki('DB')
const mediaDB = db.addCollection('Media')
const { Readable, PassThrough } = require('stream')
module.exports = srv => {
srv.before('CREATE', 'Media', req => {
const obj = mediaDB.insert({ media: '' })
req.data.id = obj.$loki
})
srv.on('UPDATE', 'Media', (req, next) => {
const url = req._.req.path
if (url.includes('content')) {
const id = req.data.id
const obj = mediaDB.get(id)
if (!obj) {
req.reject(404, 'No record found for the ID')
return
}
const stream = new PassThrough()
const chunks = []
stream.on('data', chunk => {
chunks.push(chunk)
})
stream.on('end', () => {
obj.media = Buffer.concat(chunks).toString('base64')
mediaDB.update(obj)
})
req.data.content.pipe(stream)
} else return next()
})
srv.on('READ', 'Media', (req, next) => {
const url = req._.req.path
if (url.includes('content')) {
const id = req.data.id
const mediaObj = mediaDB.get(id)
if (!mediaObj) {
req.reject(404, 'Media not found for the ID')
return
}
const decodedMedia = new Buffer(
mediaObj.media.split(';base64,').pop(),
'base64'
)
return _formatResult(decodedMedia)
} else return next() //> delegate to next/default handlers
})
srv.on('DELETE', 'Media', (req, next) => {
const id = req.data.id
mediaDB
.chain()
.find({ $loki: id })
.remove()
return next() //> delegate to next/default handlers
})
function _formatResult (decodedMedia) {
const readable = new Readable()
const result = new Array()
readable.push(decodedMedia)
readable.push(null)
result.push({ value: readable })
return result
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -1,35 +0,0 @@
### Requires REST Client for VS Code
### https://marketplace.visualstudio.com/items?itemName=humao.rest-client
###
@protocol = http
@host = localhost
@port = 4004
### Read Pictures
GET {{protocol}}://{{host}}:{{port}}/media-server/Media
Authorization: Basic admin:
### Create Picture with mediatype
POST {{protocol}}://{{host}}:{{port}}/media-server/Media
Authorization: Basic admin:
Accept: application/json
Content-Type: application/json
{
"id": 1,
"mediaType": "image/png"
}
### Upload Binary PNG
PUT {{protocol}}://{{host}}:{{port}}/media-server/Media(1)/content
Authorization: Basic admin:
Content-Type: image/png
< ./Test.png
### Read Binary
GET {{protocol}}://{{host}}:{{port}}/media-server/Media(1)/content
Authorization: Basic admin:
### Delete Image
DELETE {{protocol}}://{{host}}:{{port}}/media-server/Media(1)
Authorization: Basic admin:

View File

@@ -1 +0,0 @@
@sap:registry=http://nexus.wdf.sap.corp:8081/nexus/repository/build.milestones.npm/

View File

@@ -1,15 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"SirTobi.pegjs-language",
"tamuratak.vscode-pegjs",
"joeandaverde.vscode-pegjs-live"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +0,0 @@
const fs = require("fs");
const path = require("path");
const peg = require("pegjs");
const pegGrammarPath = path.join(__dirname, "/odata2cqn.pegjs");
const odataPegGrammar = fs.readFileSync(pegGrammarPath, {
encoding: "utf8",
flag: "r",
});
const parser = peg.generate(odataPegGrammar);
module.exports = {
parse: {
url: parser.parse,
},
to: {
cqn: parser.parse,
url: (cqn) => pending(cqn)
},
serialize: (data) => pending(data),
deserialize: (body) => pending(body),
}
const pending = ()=>{
throw new Error ('Not yet implemented')
}

View File

@@ -1,200 +0,0 @@
/** ------------------------------------------
* This is a peg.js adaptation of the https://github.com/oasis-tcs/odata-abnf/blob/master/abnf/odata-abnf-construction-rules.txt
* which directly constructs CQN out of parsed sources.
*
* NOTE:
* In contrast to the OData ABNF source, which uses very detailedsemantic rules,
* this adaptation uses rather generic syntactic rules only, e.g. not distinguishing
* betwenn Collection Navigation or not knowing individual function names.
* This is to be open to future enhancements of the OData standard, as well as
* to improve error messages. For example a typo in a function name could be
* reported specifically instead of throwing a generic parser error.
*
* See also: https://docs.microsoft.com/en-us/odata/concepts/queryoptions-overview
* Future test cases http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/abnf/odata-abnf-testcases.xml
*
* Limitations: Type, Geo functions are not supported,
* maxdatetime, mindatetime, fractionalseconds,
* totaloffsetminutes, date, totalseconds,
* floor, ceiling also are not supported by CAP
*
* Examples:
* Books
* Books/201
* Books?$select=ID,title&$expand=author($select=name)&$filter=stock gt 1&$orderby=title
*/
// ---------- JavaScript Helpers -------------
{
const stack=[]
let SELECT
}
// ---------- Entity Paths ---------------
ODataRelativeURI // Note: case-sensitive!
= (p:path { SELECT = { from:p } })
( o"?"o QueryOption ( o'&'o QueryOption )* )? {
if (SELECT.expand) {
SELECT.columns = SELECT.expand
delete SELECT.expand
}
return { SELECT }
}
path
= crv:$("$count"/"$ref"/"$value") {return {ref:[crv]}}
/ head:identifier filter:(OPEN args CLOSE)? tail:( '/' p:path {return p} )? {
const ref = [ filter ? { id:head, where:filter[1] } : head ]
if (tail) ref.push (...tail.ref)
return {ref}
}
args
= val:( number / integer / string ) {return [{val}]}
/ ref:identifier o"="o val:( number / integer / string ) more:( COMMA args )? {
const args = [ {ref}, '=', {val} ]
if (more) args.push ('and', ...more[1])
return args
}
ref "a reference"
= head:identifier tail:( '/' identifier )* {
return { ref:[ head, ...tail ] }
}
//
// ---------- Query Options ------------
QueryOption = ExpandOption
ExpandOption =
"$select=" o select ( COMMA select )* /
"$expand=" o expand ( COMMA expand )* /
"$filter=" o filter /
"$orderby=" o orderby /
"$top=" o top /
"$skip=" o skip /
"$search=" o search /
"$count=" o count
select
= col:ref {
(SELECT.expand || (SELECT.expand = [])).push(col)
return col
}
expand =
( c:select {c.expand='*'} )
( // --- nested query options, if any
(OPEN {
stack.push (SELECT)
SELECT = SELECT.expand[SELECT.expand.length-1]
SELECT.expand = []
})
ExpandOption ( o";"o ExpandOption )*
(CLOSE {
SELECT = stack.pop()
})
)? // --- end of nested query options
( COMMA expand )?
top
= val:integer {
(SELECT.limit || (SELECT.limit={})).rows = {val}
}
skip
= val:integer {
(SELECT.limit || (SELECT.limit={})).offset = {val}
}
search
= p:search_clause {SELECT.search = p}
search_clause = p:( n:NOT? {return n?[n]:[]} )(
OPEN xpr:search_clause CLOSE {p.push({xpr})}
/ val:(identifier/string) {p.push({val})}
)( ao:(AND/OR) more:search_clause {p.push(ao,...more)} )*
{return p}
filter
= p:where_clause {SELECT.where = p}
where_clause = p:( n:NOT? {return n?[n]:[]} )(
OPEN xpr:where_clause CLOSE {p.push({xpr})}
/ comp:comparison {p.push(...comp)}
/ func:boolish {p.push(func)}
)( ao:(AND/OR) more:where_clause {p.push(ao,...more)} )*
{return p}
orderby
= ref:ref sort:( _ s:$("asc"/"desc") {return s})? {
SELECT.orderby = $(ref, sort && {sort})
}
count
= c:$[^,?&()]+ { SELECT.count = true }
//
// ---------- Expressions ------------
comparison "a comparison"
= a:operand _ o:$("eq"/"ne"/"lt"/"gt"/"le"/"ge") _ b:operand {
const op = { eq:'=', ne:'!=', lt:'<', gt:'>', le:'<=', ge:'>=' }[o]||o
return [ a, op, b ]
}
operand "an operand"
= val:number {return Number.isSafeInteger(val) ? {val} : { val:String(val), literal:'number' }}
/ val:string {return {val}}
/ function
/ ref
function "a function call"
= func:$[a-z]+ OPEN a:operand more:( COMMA o:operand {return o} )* CLOSE
{ return { func, args:[a,...more] }}
boolish "a boolean function"
= func:("contains"/"endswith"/"startswith") OPEN a:operand COMMA b:operand CLOSE
{ return { func, args:[a,b] }}
NOT = o "not"i _ {return 'not'}
AND = _ "and"i _ {return 'and'}
OR = _ "or"i _ {return 'or'}
//
// ---------- Literals -----------
string "Edm.String"
= "'" s:$("''"/[^'])* "'"
{return s.replace(/''/g,"'")}
number
= x:$( [+-]? [0-9]+ ("."[0-9]+)? ("e"[0-9]+)? )
{return Number(x)}
integer
= x:$( [+-]? [0-9]+ )
{return parseInt(x)}
identifier
= $([a-zA-Z][_a-zA-Z0-9]*)
//
// ---------- Punctuation ----------
COLON = o":"o
COMMA = o","o
SEMI = o";"o
OPEN = o"("o
CLOSE = o")"
//
// ---------- Whitespaces -----------
o "optional whitespaces" = $[ \t\n]*
_ "mandatory whitespaces" = $[ \t\n]+
//
// ------------------------------------

View File

@@ -1,15 +0,0 @@
{
"name": "@sap/cds-odata",
"version": "1.0.0",
"dependencies": {
"@sap/cds-compiler": "latest",
"@sap/cds": "^4.4.10"
},
"scripts": {
"postinstall": "rm -fr node_modules/@sap/cds/node_modules"
},
"devDependencies": {
"pegjs": "^0.10.0"
},
"private": true
}

View File

@@ -1,105 +0,0 @@
const cds = require("@sap/cds/lib"), {expect} = cds.test
cds.odata = require("../lib")
describe("$filter", () => {
describe("comparing expressions", () => {
const types = {
strings: "'some string'",
integers: 11,
decimals: 0.99,
// ...
}
it.each(Object.keys(types))("should support expressions with %s", (t) => {
expect (cds.odata.parse.url(`Foo?$filter=bar eq ${types[t]}`))
.to.eql (cds.parse.cql(`SELECT from Foo where bar = ${types[t]}`))
})
const operators = {
eq: '=',
lt: '<',
le: '<=',
gt: '>',
ge: '>=',
ne: '!=',
// ...
}
it.each(Object.keys(operators))("should support comparison operator '%s'", (op) => {
expect (cds.odata.parse.url(`Foo?$filter=bar ${op} 11`))
.to.eql (cds.parse.cql(`SELECT from Foo where bar ${operators[op]} 11`))
})
});
describe("logical expressions", () => {
it.each(['and','or'])("should support '%s'", (t) => {
expect (cds.odata.parse.url(`Foo?$filter=bar lt 11 ${t} name eq 'some name'`))
.to.eql (cds.parse.cql(`SELECT from Foo where bar < 11 ${t} name = 'some name'`))
})
it("should support 'not'", () => {
// REVISIT: We need to check with the Node.js team why they translated that to the equivalent of:
// not name like concat('%','sunny','%') escape '^'
expect (cds.odata.parse.url(`Foo?$filter= not contains(name,'sunny')`))
.to.eql (cds.parse.cql(`SELECT from Foo where not contains(name,'sunny')`))
});
// REVISIT: wait for compiler v2
it("should support group expr", () => {
expect (cds.odata.parse.url(`Foo?$filter= (unitPrice gt 11 and length(name) eq 12) or name eq 'Restless and Wild'`))
.to.eql (cds.parse.cql(`SELECT from Foo where (unitPrice > 11 and length(name) = 12) or name = 'Restless and Wild'`))
});
});
describe("function expressions", () => {
it("should support contains", () => {
expect (cds.odata.parse.url(`Foo?$filter= contains(name,'sunny')`))
.to.eql (cds.parse.cql(`SELECT from Foo where contains(name,'sunny')`))
});
it("should support startswith", () => {
expect (cds.odata.parse.url(`Foo?$filter= startswith(name,'sunny')`))
.to.eql (cds.parse.cql(`SELECT from Foo where startswith(name,'sunny')`))
});
it("should support endswith", () => {
expect (cds.odata.parse.url(`Foo?$filter= endswith(name,'sunny')`))
.to.eql (cds.parse.cql(`SELECT from Foo where endswith(name,'sunny')`))
});
it("should support length", () => {
expect (cds.odata.parse.url(`Foo?$filter= length(name) lt 11`))
.to.eql (cds.parse.cql(`SELECT from Foo where length(name) < 11`))
});
it("should support indexof", () => {
expect (cds.odata.parse.url(`Foo?$filter= indexof(name,'x') eq 11`))
.to.eql (cds.parse.cql(`SELECT from Foo where indexof(name,'x') = 11`))
});
it("should support substring", () => {
expect (cds.odata.parse.url(`Foo?$filter= substring(name,1) eq 'foo'`))
.to.eql (cds.parse.cql(`SELECT from Foo where substring(name,1) = 'foo'`))
});
it.each(['tolower','toupper','trim'])("should support '%s'", (fn) => {
expect (cds.odata.parse.url(`Foo?$filter= ${fn}(name) eq 'foo'`))
.to.eql (cds.parse.cql(`SELECT from Foo where ${fn}(name) = 'foo'`))
});
it("should support 'day'", () => {
expect (cds.odata.parse.url(`Foo?$filter= day(name) eq 11`))
.to.eql (cds.parse.cql(`SELECT from Foo where day(name) = 11`))
});
it("should support concat", () => {
expect (cds.odata.parse.url(`Foo?$filter= concat(name,'o') eq 'foo'`))
.to.eql (cds.parse.cql(`SELECT from Foo where concat(name,'o') = 'foo'`))
});
});
});

View File

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

View File

@@ -1,39 +0,0 @@
<!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>

View File

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

View File

@@ -1,92 +0,0 @@
////////////////////////////////////////////////////////////////////////////
//
// 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.
//
////////////////////////////////////////////////////////////////////////////
using { OrdersService } from '../../srv/orders-service';
@odata.draft.enabled
annotate OrdersService.Orders with @(
UI: {
SelectionFields: [ createdAt, createdBy ],
LineItem: [
{Value: OrderNo, Label:'OrderNo'},
{Value: buyer, Label:'Customer'},
{Value: createdAt, Label:'Date'}
],
HeaderInfo: {
TypeName: 'Order', TypeNamePlural: 'Orders',
Title: {
Label: 'Order number ', //A label is possible but it is not considered on the ObjectPage yet
Value: OrderNo
},
Description: {Value: createdBy}
},
Identification: [ //Is the main field group
{Value: createdBy, Label:'Customer'},
{Value: createdAt, Label:'Date'},
{Value: OrderNo },
],
HeaderFacets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Created}', Target: '@UI.FieldGroup#Created'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Modified}', Target: '@UI.FieldGroup#Modified'},
],
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>OrderItems}', Target: 'Items/@UI.LineItem'},
],
FieldGroup#Details: {
Data: [
{Value: currency.code, Label:'Currency'}
]
},
FieldGroup#Created: {
Data: [
{Value: createdBy},
{Value: createdAt},
]
},
FieldGroup#Modified: {
Data: [
{Value: modifiedBy},
{Value: modifiedAt},
]
},
},
) {
createdAt @UI.HiddenFilter:false;
createdBy @UI.HiddenFilter:false;
};
annotate OrdersService.Orders_Items with @(
UI: {
LineItem: [
{Value: product_ID, Label:'Product ID'},
{Value: title, Label:'Product Title'},
{Value: price, Label:'Unit Price'},
{Value: amount, Label:'Quantity'},
],
Identification: [ //Is the main field group
{Value: amount, Label:'Amount'},
{Value: title, Label:'Product'},
{Value: price, Label:'Unit Price'},
],
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>OrderItems}', Target: '@UI.Identification'},
],
},
) {
amount @(
Common.FieldControl: #Mandatory
);
};

View File

@@ -1,8 +0,0 @@
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
"use strict";
return AppComponent.extend("orders.Component", {
metadata: { manifest: "json" }
});
});
/* eslint no-undef:0 */

View File

@@ -1,3 +0,0 @@
ID;createdAt;createdBy;buyer;OrderNo;currency_code
7e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-01-31;john.doe@test.com;john.doe@test.com;1;EUR
64e718c9-ff99-47f1-8ca3-950c850777d4;2019-01-30;jane.doe@test.com;jane.doe@test.com;2;EUR
1 ID createdAt createdBy buyer OrderNo currency_code
2 7e2f2640-6866-4dcf-8f4d-3027aa831cad 2019-01-31 john.doe@test.com john.doe@test.com 1 EUR
3 64e718c9-ff99-47f1-8ca3-950c850777d4 2019-01-30 jane.doe@test.com jane.doe@test.com 2 EUR

View File

@@ -1,4 +0,0 @@
ID;up__ID;amount;product_ID;title;price
58040e66-1dcd-4ffb-ab10-fdce32028b79;7e2f2640-6866-4dcf-8f4d-3027aa831cad;1;201;Wuthering Heights;11.11
64e718c9-ff99-47f1-8ca3-950c850777d4;7e2f2640-6866-4dcf-8f4d-3027aa831cad;1;271;Catweazle;15
e9641166-e050-4261-bfee-d1e797e6cb7f;64e718c9-ff99-47f1-8ca3-950c850777d4;2;252;Eleonora;28
1 ID up__ID amount product_ID title price
2 58040e66-1dcd-4ffb-ab10-fdce32028b79 7e2f2640-6866-4dcf-8f4d-3027aa831cad 1 201 Wuthering Heights 11.11
3 64e718c9-ff99-47f1-8ca3-950c850777d4 7e2f2640-6866-4dcf-8f4d-3027aa831cad 1 271 Catweazle 15
4 e9641166-e050-4261-bfee-d1e797e6cb7f 64e718c9-ff99-47f1-8ca3-950c850777d4 2 252 Eleonora 28

View File

@@ -1,26 +0,0 @@
using { Currency, User, managed, cuid } from '@sap/cds/common';
namespace sap.capire.orders;
entity Orders : cuid, managed {
OrderNo : String @title:'Order Number'; //> readable key
Items : Composition of many Orders_Items on Items.up_ = $self;
buyer : User;
currency : Currency;
}
entity Orders_Items {
key ID : UUID;
up_ : Association to Orders;
product : Association to Products @assert.integrity:false; // REVISIT: this is a temporary workaround for a glitch in cds-runtime
amount : Integer;
title : String;
price : Double;
}
/** This is a stand-in for arbitrary ordered Products */
entity Products @(cds.persistence.skip:'always') {
key ID : String;
}
// Activate extension package
using from '@capire/common';

View File

@@ -1,6 +0,0 @@
/*
This model controls what gets exposed
*/
namespace sap.capire.orders;
using from './srv/orders-service';
using from './db/schema';

View File

@@ -1,8 +0,0 @@
{
"name": "@capire/orders",
"version": "1.0.0",
"dependencies": {
"@capire/common": "*",
"@sap/cds": "^4.3.0"
}
}

View File

@@ -1,37 +0,0 @@
const cds = require ('@sap/cds')
class OrdersService extends cds.ApplicationService {
/** register custom handlers */
init(){
const { Orders_Items:OrderItems } = this.entities
this.before ('UPDATE', 'Orders', async function(req) {
const { ID, Items } = req.data
if (Items) for (let { product_ID, amount } of Items) {
const { amount:before } = await cds.tx(req).run (
SELECT.one.from (OrderItems, oi => oi.amount) .where ({up__ID:ID, product_ID})
)
if (amount != before) await this.orderChanged (product_ID, amount-before)
}
})
this.before ('DELETE', 'Orders', async function(req) {
const { ID } = req.data
const Items = await cds.tx(req).run (
SELECT.from (OrderItems, oi => { oi.product_ID, oi.amount }) .where ({up__ID:ID})
)
if (Items) await Promise.all (Items.map(it => this.orderChanged (it.product_ID, -it.amount)))
})
return super.init()
}
/** order changed -> broadcast event */
orderChanged (product, deltaAmount) {
// Emit events to inform subscribers about changes in orders
console.log ('> emitting:', 'OrderChanged', { product, deltaAmount })
return this.emit ('OrderChanged', { product, deltaAmount })
}
}
module.exports = OrdersService

View File

@@ -1,40 +1,28 @@
{
"name": "@capire/samples",
"version": "2.0.0",
"description": "A monorepo with several samples for CAP.",
"repository": "https://github.com/sap-samples/cloud-cap-samples.git",
"name": "@sap/capire-samples",
"description": "The umbrella project for all samples to easily setup for local development and tests.",
"repository": "https://github.com/SAP-samples/cloud-cap-samples.git",
"author": "daniel.hutzel@sap.com",
"private": true,
"scripts": {
"lerna": "npx --no-install lerna -v > /dev/null || npm i lerna --no-save",
"install": "(npm -s run lerna) && lerna bootstrap --hoist",
"cleanup": "lerna clean -y && rm -fr node_modules",
"bookshop": "cds watch packages/bookshop",
"bookshop-enhanced": "cds watch packages/bookshop-enhanced",
"reviews-service": "cds watch packages/reviews-service",
"bookstore": "cds watch packages/bookstore"
},
"dependencies": {
"@capire/bookshop": "./bookshop",
"@capire/common": "./common",
"@capire/fiori": "./fiori",
"@capire/hello": "./hello",
"@capire/media": "./media",
"@capire/orders": "./orders",
"@capire/reviews": "./reviews"
"@sap/cds": "latest",
"express": "*"
},
"devDependencies": {
"pegjs": "^0.10.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-subset": "^1.6.0",
"sqlite3": "^5"
"sqlite3": "*"
},
"scripts": {
"registry": "cd .registry && node server.js",
"bookshop": "cds watch bookshop",
"fiori": "cds watch fiori",
"media": "cds watch media",
"mocha": "npx mocha || echo",
"jest": "npx jest",
"test": "npm run jest --silent"
"--add-these-to-devDependencies-for-tests": {
"@types/jest": "*",
"jest": "*"
},
"mocha": {
"parallel": true
},
"jest": {
"testEnvironment": "node"
},
"license": "SAP SAMPLE CODE LICENSE",
"private": true
"license": "SAP SAMPLE CODE LICENSE"
}

View File

@@ -0,0 +1 @@
using from '@sap/capire-bookshop/app';

View File

@@ -0,0 +1,25 @@
/*
In this model we demonstrate how to add Genres to Books in
as if it was an external extension. For example we use
CDS Aspects' to extend the core domain model's Books entity
as well as the AdminService.
*/
namespace sap.capire.bookshop;
using { sap.capire.reviews.ReviewsService as external } from '@sap/capire-reviews';
using { sap.capire.bookshop.Books } from '@sap/capire-bookshop/db/schema';
using { sap.common.CodeList } from '@sap/cds/common';
// Extending Books by Reviews and Genres
extend Books with {
reviews : Composition of many external.Reviews on reviews.subject = ID;
rating : external.Reviews.rating;
genre : Association to Genres;
}
// Hierarchical Code List for Genres
entity Genres : CodeList {
key ID : Integer;
children : Composition of many Genres on children.parent = $self;
parent : Association to Genres;
}

View File

@@ -0,0 +1,29 @@
{
"name": "@sap/capire-bookshop-enhanced",
"version": "1.0.0",
"description": "A sample for extending a base application package, in this case bookshop, e.g. in context of verticalization or customization.",
"repository": "https://github.com/SAP-samples/cloud-cap-samples.git",
"license": "SAP SAMPLE CODE LICENSE",
"dependencies": {
"@sap/capire-bookshop": "^1.0.0",
"@sap/capire-reviews": "^1.0.0",
"@sap/cds": "latest",
"express": "*"
},
"scripts": {
"reviews-service": "PORT=5005 cds run ../reviews-service --bind --in-memory?",
"start": "cds run --in-memory?",
"watch": "cds watch"
},
"cds": {
"requires": {
"sap.capire.reviews.ReviewsService": {
"kind": "odata",
"model": "@sap/capire-reviews"
},
"messaging": {
"kind": "file-based-messaging"
}
}
}
}

View File

@@ -0,0 +1,9 @@
namespace sap.capire.bookshop;
using { AdminService } from '@sap/capire-bookshop/srv/admin-service';
using { sap.capire.bookshop } from '../db/schema';
@impl:'srv/services'
extend service AdminService with {
entity Genres as projection on bookshop.Genres;
}

View File

@@ -0,0 +1,30 @@
const cds = require ('@sap/cds')
module.exports = cds.service.impl (async()=>{
const ReviewsService = await cds.connect.to ('sap.capire.reviews.ReviewsService')
const CatalogService = await cds.connect.to ('CatalogService')
const db = await cds.connect.to ('db')
// import model definitions from connected services to work with subsequently
const { Books } = db.entities
const { Reviews } = ReviewsService.entities
CatalogService.impl (srv => {
// delegate requests to read reviews to ReviewsService
srv.on ('READ', 'Books/reviews', (req) => {
const [ subject ] = req.params
const tx = ReviewsService.transaction (req)
return tx.run (SELECT.from (Reviews) .where ({subject}))
})
})
// react on event messages from reviews service
ReviewsService.on ('reviewed', (msg) => {
console.debug ('> received:', msg.event, msg.data)
const { subject, rating } = msg.data
const tx = db // TODO: db.transaction (msg)
return tx.run (UPDATE (Books, subject) .with ({rating}))
// return tx.update (Books, subject) .with ({rating})
})
})

View File

@@ -3,15 +3,10 @@
# Genres
#
GET http://localhost:4004/test/Genres?
GET http://localhost:4004/admin/Genres?
###
GET http://localhost:4004/test/Genres?
&$filter=parent_ID eq null&$select=name
&$expand=children($select=name)
###
POST http://localhost:4004/test/Genres?
POST http://localhost:4004/admin/Genres?
Content-Type: application/json
{ "ID":100, "name":"Some Sample Genres...", "children":[
@@ -26,13 +21,13 @@ Content-Type: application/json
]}
###
GET http://localhost:4004/test/Genres(100)?
GET http://localhost:4004/admin/Genres(100)?
# &$expand=children
# &$expand=children($expand=children($expand=children($expand=children)))
###
DELETE http://localhost:4004/test/Genres(103)
DELETE http://localhost:4004/admin/Genres(103)
###
DELETE http://localhost:4004/test/Genres(100)
DELETE http://localhost:4004/admin/Genres(100)
###

View File

@@ -0,0 +1,32 @@
#################################################
#
# Reviews Service
#
### Use this one for ReviewsService running as a separate process
# Note: use 5005 instead of 4004 in case of separate service
POST http://localhost:5005/reviews/Reviews
# POST http://localhost:4004/reviews/Reviews
Content-Type: application/json;IEEE754Compatible=true
{"subject":"201", "rating":"5", "title":"boo"}
### Direct Request to ReviewsService
# Note: use 5005 instead of 4004 in case of separate service
GET http://localhost:5005/reviews/Reviews?
# GET http://localhost:4004/reviews/Reviews?
# &$filter=subject eq '201'
### Request to CatalogService > delegated to ReviewsService
GET http://localhost:4004/browse/Books(201)/reviews
### Alternative OData URL
GET http://localhost:4004/browse/Books/201/reviews
###
GET http://localhost:4004/browse/Books(201)?
&$select=ID,title,rating
# &$expand=reviews
# Note: the latter only works in case of ReviewsService in same process

View File

@@ -0,0 +1,3 @@
{
"printWidth": 120
}

View File

@@ -0,0 +1,65 @@
# Bookshop With Address Data From SAP S/4HANA
This is an extended bookshop with business-partner address data from SAP S/4HANA.
When the user creates an order and uses the value help of the shipping address,
a synchronous request to SAP S/4HANA is triggered yielding all possible addresses
belonging to this business partner. Once an address is selected, its data
is replicated into a local database. To keep data in sync, an event handler
is registered which listens to all changes of business partners and updates the
local database table.
## Prerequisites
`@sap/cds` >= 1.30
## Running With Mocks
Just execute the following command in the `bookshop` folder.
```
cds run --in-memory --with-mocks
```
## Running With an S/4HANA Backend
To run your app in non-mock mode you need an SAP S/4HANA Cloud system and connect it to your SAP Cloud Platform. You can use the
[SAP Cloud Platform Extension Factory](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/346864df64f24011b49abee07bbd79af.html) to automate parts of this task. You need to enable synchronous APIs as well as events that are sent whenever business partners are changed.
To run the app locally, you need to create a `default-env.json` file in the `bookshop` folder containing the binding information (credentials of Enterprise Messaging as well as the destination to the business-partner service).
Provide the credentials in the `cds.requires` section of the `package.json` file in the `bookshop` folder, e.g.
```json
"cds": {
"requires": {
"API_BUSINESS_PARTNER": {
"kind": "odata",
"model": "srv/external",
"credentials": {
"destination": "cap-api098"
}
},
"messaging": {
"kind": "enterprise-messaging",
"credentials": {
"prefix": "sap/S4HANAOD/c098/BO"
}
}
}
}
```
Here, `destination` is the destination of your business-partner service and `prefix` is the prefix
of the topic of the events.
Then simply run the following command in the `bookshop` folder.
```
cds run --in-memory
```
## User Flow
After starting the app, go to http://localhost:4004/fiori.html#Shell-home and open the app `Manage Orders` to create an order.
Use the value help of the shipping address to select an address. Create an order item and save the order.
Then change the address of your business partner (in the mocked case you can trigger the PATCH request in `req.http` ). Refresh
the object page of your order and see the change.

View File

@@ -0,0 +1,22 @@
Books = Books
Book = Book
ID = ID
Title = Title
Author = Author
AuthorID = Author ID
Stock = Stock
Name = Name
AuthorName = Author's Name
Authors = Authors
Order = Order
OrderItems = Order Items
Orders = Orders
Price = Price
shippingAddress = Shipping Address
cityName = City Name
houseNumber = House Number
streetName = Street Name
postalCode = Postal Code
country = Country
AddressID = Address ID
contact = Contact

View File

@@ -0,0 +1,37 @@
using AdminService from '../../srv/admin-service';
////////////////////////////////////////////////////////////////////////////
//
// Books Object Page
//
annotate AdminService.Books with @(
UI: {
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>General}', Target: '@UI.FieldGroup#General'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Admin}', Target: '@UI.FieldGroup#Admin'},
],
FieldGroup#General: {
Data: [
{Value: title},
{Value: author_ID},
{Value: descr},
]
},
FieldGroup#Details: {
Data: [
{Value: stock},
{Value: price},
{Value: currency_code, Label: '{i18n>Currency}'},
]
},
FieldGroup#Admin: {
Data: [
{Value: createdBy},
{Value: createdAt},
{Value: modifiedBy},
{Value: modifiedAt}
]
}
}
);

View File

@@ -0,0 +1,22 @@
sap.ui.define(["sap/fe/AppComponent"], ac => ac.extend("admin.Component", {
metadata:{ manifest:'json' }
}))
// sap.ui.define (["sap/ui/core/UIComponent"], ui5 => ui5.extend("bookshop.Component", {
// metadata: { manifest: "json" }
// }))
// sap.ui.define (["sap/ui/generic/app/AppComponent"], ui5 => ui5.extend("bookshop.Component", {
// metadata: { manifest: "json" }
// }))
// jQuery.sap.declare("bookshop.Component");
// sap.ui.getCore().loadLibrary("sap.ui.generic.app");
// jQuery.sap.require("sap.ui.generic.app.AppComponent");
// sap.ui.generic.app.AppComponent.extend("bookshop.Component", {
// metadata: {
// manifest: "json"
// }
// });
/* eslint no-undef:0 */

View File

@@ -24,7 +24,7 @@
"sap.ui5": {
"dependencies": {
"libs": {
"sap.fe.templates": {}
"sap.fe": {}
}
},
"models": {

View File

@@ -1,4 +1,4 @@
using CatalogService from '@capire/bookshop';
using CatalogService from '../../srv/cat-service';
////////////////////////////////////////////////////////////////////////////
//
@@ -7,8 +7,6 @@ using CatalogService from '@capire/bookshop';
annotate CatalogService.Books with @(
UI: {
HeaderInfo: {
TypeName: 'Book',
TypeNamePlural: 'Books',
Description: {Value: author}
},
HeaderFacets: [
@@ -42,7 +40,6 @@ annotate CatalogService.Books with @(
LineItem: [
{Value: title},
{Value: author, Label:'{i18n>Author}'},
{Value: genre.name},
{Value: price},
{Value: currency.symbol, Label:' '},
]

View File

@@ -0,0 +1,22 @@
sap.ui.define(["sap/fe/AppComponent"], ac => ac.extend("bookshop.Component", {
metadata:{ manifest:'json' }
}))
// sap.ui.define (["sap/ui/core/UIComponent"], ui5 => ui5.extend("bookshop.Component", {
// metadata: { manifest: "json" }
// }))
// sap.ui.define (["sap/ui/generic/app/AppComponent"], ui5 => ui5.extend("bookshop.Component", {
// metadata: { manifest: "json" }
// }))
// jQuery.sap.declare("bookshop.Component");
// sap.ui.getCore().loadLibrary("sap.ui.generic.app");
// jQuery.sap.require("sap.ui.generic.app.AppComponent");
// sap.ui.generic.app.AppComponent.extend("bookshop.Component", {
// metadata: {
// manifest: "json"
// }
// });
/* eslint no-undef:0 */

View File

@@ -24,7 +24,7 @@
"sap.ui5": {
"dependencies": {
"libs": {
"sap.fe.templates": {}
"sap.fe": {}
}
},
"models": {

View File

@@ -0,0 +1,84 @@
/*
Common Annotations shared by all apps
*/
using { sap.capire.bookshop as my } from '../db/schema';
////////////////////////////////////////////////////////////////////////////
//
// Books Lists
//
annotate my.Books with @(
UI: {
Identification: [{Value:title}],
SelectionFields: [ ID, author_ID, price, currency_code ],
LineItem: [
{Value: ID},
{Value: title},
{Value: author.name, Label:'{i18n>Author}'},
{Value: stock},
{Value: price},
{Value: currency.symbol, Label:' '},
]
}
) {
author @ValueList.entity:'Authors';
};
annotate my.Authors with @(
UI: {
Identification: [{Value:name}],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Books Details
//
annotate my.Books with @(
UI: {
HeaderInfo: {
TypeName: '{i18n>Book}',
TypeNamePlural: '{i18n>Books}',
Title: {Value: title},
Description: {Value: author.name}
},
}
);
////////////////////////////////////////////////////////////////////////////
//
// Books Elements
//
annotate my.Books with {
ID @title:'{i18n>ID}' @UI.HiddenFilter;
title @title:'{i18n>Title}';
author @title:'{i18n>AuthorID}';
price @title:'{i18n>Price}';
stock @title:'{i18n>Stock}';
descr @UI.MultiLineText;
}
////////////////////////////////////////////////////////////////////////////
//
// Authors Elements
//
annotate my.Authors with {
ID @title:'{i18n>ID}' @UI.HiddenFilter;
name @title:'{i18n>AuthorName}';
}
annotate my.Addresses with {
ID @title:'{i18n>AddressID}';
contact @title:'{i18n>contact}';
@readonly cityName @title:'{i18n>cityName}';
@readonly streetName @title:'{i18n>streetName}';
@readonly postalCode @title:'{i18n>postalCode}';
@readonly country @title:'{i18n>country}';
@readonly houseNumber @title:'{i18n>houseNumber}';
}

View File

@@ -13,7 +13,7 @@
applications: {
"browse-books": {
title: "Browse Books",
description: "w/ SAP Fiori Elements",
description: "... testing FE v42",
additionalInformation: "SAPUI5.Component=bookshop",
applicationType : "URL",
url: "/browse/webapp",
@@ -21,7 +21,7 @@
},
"manage-books": {
title: "Manage Books",
description: "w/ SAP Fiori Elements",
description: "... testing FE v42",
additionalInformation: "SAPUI5.Component=admin",
applicationType : "URL",
url: "/admin/webapp",
@@ -29,7 +29,7 @@
},
"manage-orders": {
title: "Manage Orders",
description: "w/ SAP Fiori Elements",
description: "... testing FE v42",
additionalInformation: "SAPUI5.Component=orders",
applicationType : "URL",
url: "/orders/webapp",
@@ -39,8 +39,8 @@
};
</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 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"
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

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

View File

@@ -0,0 +1,243 @@
using AdminService from '../../srv/admin-service';
annotate AdminService.Books with {
price @Common.FieldControl : #ReadOnly;
}
////////////////////////////////////////////////////////////////////////////
//
// Common
//
annotate AdminService.OrderItems with {
book @(
Common : {
Text : book.title,
FieldControl : #Mandatory
},
ValueList.entity : 'Books',
);
amount @(Common.FieldControl : #Mandatory);
}
annotate AdminService.Orders with {
shippingAddress @(Common : {
FieldControl : #Mandatory,
ValueList : {
CollectionPath : 'Addresses',
Label : 'Addresses',
SearchSupported : 'true',
Parameters : [
{
$Type : 'Common.ValueListParameterOut',
LocalDataProperty : 'shippingAddress_ID',
ValueListProperty : 'ID'
},
{
$Type : 'Common.ValueListParameterOut',
LocalDataProperty : 'shippingAddress_contact',
ValueListProperty : 'contact'
},
{
$Type : 'Common.ValueListParameterDisplayOnly',
ValueListProperty : 'postalCode'
},
{
$Type : 'Common.ValueListParameterDisplayOnly',
ValueListProperty : 'cityName'
},
{
$Type : 'Common.ValueListParameterDisplayOnly',
ValueListProperty : 'country'
},
{
$Type : 'Common.ValueListParameterDisplayOnly',
ValueListProperty : 'streetName'
},
{
$Type : 'Common.ValueListParameterDisplayOnly',
ValueListProperty : 'houseNumber'
},
]
},
});
}
////////////////////////////////////////////////////////////////////////////
//
// UI
//
annotate AdminService.Orders with @(UI : {
////////////////////////////////////////////////////////////////////////////
//
// Lists of Orders
//
SelectionFields : [
createdAt,
createdBy
],
LineItem : [
{
Value : createdBy,
Label : 'Customer'
},
{
Value : createdAt,
Label : 'Date'
}
],
////////////////////////////////////////////////////////////////////////////
//
// Order Details
//
HeaderInfo : {
TypeName : 'Order',
TypeNamePlural : 'Orders',
Title : {
Label : 'Order number ', //A label is possible but it is not considered on the ObjectPage yet
Value : OrderNo
},
Description : {Value : createdBy}
},
Identification : [ //Is the main field group
// labels not considered
{
Value : createdBy,
Label : 'Customer'
},
{
Value : createdAt,
Label : 'Date'
},
{Value : OrderNo},
{
Value : 'shippingAddress_ID',
Label : 'Address ID'
}
],
HeaderFacets : [
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Created}',
Target : '@UI.FieldGroup#Created'
},
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Modified}',
Target : '@UI.FieldGroup#Modified'
},
],
Facets : [
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>shippingAddress}',
Target : '@UI.FieldGroup#ShippingAddress'
},
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Details}',
Target : '@UI.FieldGroup#Details'
},
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>OrderItems}',
Target : 'Items/@UI.LineItem'
},
],
FieldGroup #Details : {Data : [{
Value : currency_code,
Label : 'Currency'
}]},
FieldGroup #Created : {Data : [
{Value : createdBy},
{Value : createdAt},
]},
FieldGroup #Modified : {Data : [
{Value : modifiedBy},
{Value : modifiedAt},
]},
FieldGroup #ShippingAddress : {Data : [
{
Value : shippingAddress_ID,
Label : '{i18n>shippingAddress}'
},
{
Value : shippingAddress.houseNumber,
Label : '{i18n>houseNumber}'
},
{
Value : shippingAddress.streetName,
Label : '{i18n>streetName}'
},
{
Value : shippingAddress.cityName,
Label : '{i18n>cityName}'
},
{
Value : shippingAddress.postalCode,
Label : '{i18n>postalCode}'
},
]},
},
Common.SideEffects : {
EffectTypes : #ValueChange,
SourceProperties : [shippingAddress_ID],
TargetProperties : [
shippingAddress.country,
shippingAddress.houseNumber,
shippingAddress.streetName,
shippingAddress.cityName,
shippingAddress.postalCode
]
},
) {
createdAt @UI.HiddenFilter : false;
createdBy @UI.HiddenFilter : false;
};
//The enity types name is AdminService.my_bookshop_OrderItems
//The annotations below are not generated in edmx WHY?
annotate AdminService.OrderItems with @(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 : [
{
Value : book_ID,
Label : 'Book'
},
//The following entry is only used to have the assoication followed in the read event
{
Value : book.price,
Label : 'Book Price'
},
{
Value : amount,
Label : 'Quantity'
},
],
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'
},
],
Facets : [{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>OrderItems}',
Target : '@UI.Identification'
}, ],
}, );

View File

@@ -0,0 +1,22 @@
sap.ui.define(["sap/fe/AppComponent"], ac => ac.extend("orders.Component", {
metadata:{ manifest:'json' }
}))
// sap.ui.define (["sap/ui/core/UIComponent"], ui5 => ui5.extend("bookshop.Component", {
// metadata: { manifest: "json" }
// }))
// sap.ui.define (["sap/ui/generic/app/AppComponent"], ui5 => ui5.extend("bookshop.Component", {
// metadata: { manifest: "json" }
// }))
// jQuery.sap.declare("bookshop.Component");
// sap.ui.getCore().loadLibrary("sap.ui.generic.app");
// jQuery.sap.require("sap.ui.generic.app.AppComponent");
// sap.ui.generic.app.AppComponent.extend("bookshop.Component", {
// metadata: {
// manifest: "json"
// }
// });
/* eslint no-undef:0 */

Some files were not shown because too many files have changed in this diff Show More