Compare commits

...

69 Commits

Author SHA1 Message Date
nkaputnik
e08cdf7831 typo 2021-12-10 10:41:34 +01:00
nkaputnik
1ec44aaecf Fixed dependency 2021-12-10 10:40:17 +01:00
nkaputnik
a6ea2fb130 Added changes for simple field extensibility (UIFlex) 2021-12-07 11:10:12 +01:00
Daniel
1fde0c928b Fixed submit order -> returned stock was wrong 2021-11-29 15:38:37 +01:00
Christian Georgi
fa854a2411 Add hint on db-ext datasource to code tour
Signed-off-by: Christian Georgi <christian.georgi@sap.com>
2021-11-29 15:33:57 +01:00
Daniel
40b6acee6a Fixing erroneous db.model config 2021-11-29 15:33:57 +01:00
Daniel
a37f1b8e0c Reformatted package.json 2021-11-29 15:33:57 +01:00
Christian Georgi
042ddec41c Adjust code tour 2021-11-29 15:33:57 +01:00
Daniel
8d8aa6c2c9 Fixing copy errors
Signed-off-by: Daniel <daniel.hutzel@sap.com>
2021-11-29 15:33:57 +01:00
Daniel
756ed48ce3 Fiori apps with manage authors
Signed-off-by: Daniel <daniel.hutzel@sap.com>
2021-11-29 15:33:57 +01:00
Daniel
19b680ab3d Moving lifetime + age to where it is used
Signed-off-by: Daniel <daniel.hutzel@sap.com>
2021-11-29 15:33:57 +01:00
Daniel
33380c0792 Restore simple bookshop - no workarounds
Signed-off-by: Daniel <daniel.hutzel@sap.com>
2021-11-29 15:33:57 +01:00
Christian Georgi
0d5b65d93f Merge pull request #291 from gregorwolf/navigation
Demo for Semantic Navigation using the local Fiori Launchpad
2021-11-22 13:08:57 +01:00
Christian Georgi
dcf5ec6068 Merge branch 'main' into navigation 2021-11-18 16:19:57 +01:00
Christian Georgi
fb41dd100b Merge pull request #293 from SAP-samples/bookstore
In Fiori app, use bookstore consistently
2021-11-18 16:12:12 +01:00
Christian Georgi
769aa2b4f5 Adjust samples tour to bookstore 2021-11-18 15:34:59 +01:00
Christian Georgi
f6c5e620c0 In Fiori app, use bookstore consistently
This also fixes validation errors from annotation LSP, which missed some
of the bookstore elements

Signed-off-by: Christian Georgi <christian.georgi@sap.com>
2021-11-18 14:14:09 +01:00
Gregor Wolf
7013cb6296 remove name 2021-11-13 00:48:13 +01:00
Gregor Wolf
6411ce4ea3 remove redundant annotation 2021-11-13 00:48:04 +01:00
Gregor Wolf
cd914a8d87 show ID in LineItem 2021-11-13 00:41:48 +01:00
Gregor Wolf
b35e92b5bc TextArrangement : #TextOnly 2021-11-13 00:41:28 +01:00
Gregor Wolf
8350aff111 correct renameTo 2021-11-13 00:36:22 +01:00
Gregor Wolf
9aa604c5a0 remove @UI.HiddenFilter 2021-11-12 23:56:11 +01:00
Gregor Wolf
f5cb53adf8 add inbounds parameter 2021-11-12 23:48:51 +01:00
Gregor Wolf
9fe16edc55 fix manifest issues 2021-11-12 23:48:26 +01:00
Gregor Wolf
0093883f25 add Author ID column 2021-11-12 23:46:41 +01:00
Gregor Wolf
14024a4837 add semantic object for navigation 2021-11-12 22:31:58 +01:00
Gregor Wolf
2e13b37096 activate automatic load 2021-11-12 22:31:42 +01:00
Gregor Wolf
7407b0aae3 format 2021-11-12 22:04:48 +01:00
Gregor Wolf
152d088ff4 rename author to authorName 2021-11-12 21:49:39 +01:00
Gregor Wolf
19c886d6dc fix implicit redirection issue 2021-11-12 21:41:39 +01:00
Gregor Wolf
575cffe70a migrate to fioriSandboxConfig.json 2021-11-12 21:29:21 +01:00
Gregor Wolf
03869e5896 Fiori UI for Authors 2021-11-12 21:28:49 +01:00
Gregor Wolf
597afeb3d8 format, rename author to authorName 2021-11-12 21:28:23 +01:00
Gregor Wolf
d376e6e063 UI annotations for Authors 2021-11-12 21:27:40 +01:00
Gregor Wolf
2a44cfa9dc add Authors, rename author attribute 2021-11-12 21:25:33 +01:00
Gregor Wolf
7964c9aefd adjust formatting 2021-11-12 20:59:45 +01:00
Christian Georgi
ce752097d0 Merge pull request #290 from dinurp/main
Annotations for Admin.Book service
2021-11-10 08:55:47 +01:00
dinurp
e838f380c5 annotation to make language value list a drop down.
type:#Fixed was not rendering the value list as a drop down.
There was not UI validation for values entered. 
The added annotation does both these: UI validation and renders the a drop down list.
2021-11-10 11:18:41 +04:00
dinurp
e89e012eba Update bookshop/srv/admin-service.cds
Co-authored-by: Christian Georgi <chgeo@users.noreply.github.com>
2021-11-10 11:12:18 +04:00
Christian Georgi
bf0f57efe5 Merge branch 'main' into main 2021-11-09 17:55:55 +01:00
Dinu
40ea057f7f Annotation for value list for languages for locale 2021-11-09 15:46:22 +04:00
Dinu
31d2f4c932 Avoid popup for ID in Fiori preview for Books. 2021-11-09 14:01:53 +04:00
Daniel Hutzel
8cc2db7118 Using managed compositions for Order.Items (#273)
* Using managed compositions for Order.Items
Co-authored-by: sjvans <30337871+sjvans@users.noreply.github.com>
2021-11-08 17:41:33 +01:00
Daniel Hutzel
482b71e60b Remove workaround for integrity check (#173)
* Remove workaround for integrity check
2021-11-08 14:23:41 +01:00
Daniel Hutzel
7536451e29 Enhanced cds.ql tests (#228)
* Enhanced cds.ql tests
2021-11-08 13:49:43 +01:00
Daniel
2703e8b999 Removed authorization workaround 2021-11-08 13:19:12 +01:00
Daniel
ac2e4dd6f9 Updating package-lock to v2 2021-11-08 13:17:31 +01:00
PierreFritsch
c718fa400d import {Request} => import type {Request} 2021-11-08 13:14:02 +01:00
Pierre Fritsch
730da6e435 hello TypeScript: Define type of req object
Make it clearer that `req` is a CDS `Request` object.
2021-11-08 13:14:02 +01:00
Daniel
7d9b4f7a5d Fixes for Node16 2021-11-05 17:01:59 +01:00
Daniel
35d3708592 Fixes for Node16 2021-11-05 15:49:05 +01:00
Daniel
eaaf0d29a5 Updated package-lock.json 2021-11-04 10:37:33 +01:00
Daniel
64fe700d1e ... 2021-11-04 10:37:33 +01:00
Daniel
5c3cec973e Using bookstore as composite app 2021-11-04 10:37:33 +01:00
Daniel
680a6ae68f Introduced bookstore composite app 2021-11-04 10:37:33 +01:00
Daniel
366b0f8f9a Using flat fiori app folder 2021-11-04 10:37:33 +01:00
Daniel
b95df77b9a Fixed access to cds.entities 2021-11-04 10:37:33 +01:00
Christian Georgi
7f6b87171a Merge pull request #284 from SAP-samples/registry
NPM registry: more robust,  work on Windows
2021-10-26 22:21:32 +02:00
Christian Georgi
21e74bbbfb Add test for NPM registry 2021-10-26 22:17:25 +02:00
Christian Georgi
102b15c3cd NPM registry: more robust, work on Windows
- Run `npm rm` in correct dir
- Encode args in `npm conf rm` command for Powershell
- Don't cleanup in exit handler, this might not complete.
  Instead, do it on startup.
- Different cleanup command for Windows and Linux
- Explicitly close server, instead of relying on process.exit()
- Make scope configurable
2021-10-26 19:11:08 +02:00
Dr. David A. Kunz
73db2e96bc Merge pull request #283 from SAP-samples/ql-changes-typo-fix
fix typo in ql changes
2021-10-26 11:57:44 +02:00
Dr. David A. Kunz
7bb58ee2d5 Merge branch 'main' into ql-changes-typo-fix 2021-10-26 11:56:15 +02:00
D065023
27e82d16e0 fix typo 2021-10-26 11:55:33 +02:00
Dr. David A. Kunz
ff9bbe6d8d Merge pull request #282 from SAP-samples/ql-changes
Changes in ql for cds version 5.6.0
2021-10-26 11:32:09 +02:00
Dr. David A. Kunz
5f1b7b8cbf Merge branch 'main' into ql-changes 2021-10-26 11:27:27 +02:00
D065023
0253300557 Changes in ql for cds version 5.6.0 2021-10-26 11:22:43 +02:00
Daniel
139d957495 Added reviewed.count to messsages 2021-10-18 18:04:33 +02:00
Daniel
404427237b Updated reviews samples for messaging GA 2021-10-18 18:04:33 +02:00
58 changed files with 11913 additions and 682 deletions

View File

@@ -1,18 +1,24 @@
const { exec } = require ('child_process') const { exec } = require ('child_process')
const isWin = process.platform === 'win32'
const express = require ('express') const express = require ('express')
const fs = require ('fs') const fs = require ('fs')
const app = express() const app = express()
const { PORT=4444 } = process.env const { PORT=4444 } = process.env
const [,,port=PORT] = process.argv const [,,port=PORT,scope='@capire'] = process.argv
const cwd = __dirname const cwd = __dirname
// clean up on start (exit handler might not complete on Windows)
exec(isWin ? 'del *.tgz' : 'rm *.tgz', {cwd})
app.use('/-/:tarball', (req,res,next) => { app.use('/-/:tarball', (req,res,next) => {
console.debug ('GET', req.params) console.debug ('GET', req.params)
try { try {
const { tarball } = req.params const { tarball } = req.params
const [, pkg ] = /^capire-(\w+)/.exec(tarball) const [, pkg ] = /^\w+-(\w+)/.exec(tarball)
fs.lstat(tarball,(err => { fs.lstat(tarball,(err => {
if (err) console.debug (`npm pack ../${pkg}`)
if (err) exec(`npm pack ../${pkg}`,{cwd},next) if (err) exec(`npm pack ../${pkg}`,{cwd},next)
else next() else next()
})) }))
@@ -25,12 +31,14 @@ app.use('/-/:tarball', (req,res,next) => {
app.use('/-', express.static(__dirname)) app.use('/-', express.static(__dirname))
app.get('/*', (req,res)=>{ app.get('/*', (req,res)=>{
const urlRegex = /^\/(@\w+)\/(\w+)/
const url = decodeURIComponent(req.url) const url = decodeURIComponent(req.url)
console.debug ('GET',url) console.debug ('GET',url)
try { try {
const [, capire, pkg ] = /^\/(@capire)\/(\w+)/.exec(url) if (!urlRegex.test(url)) return res.sendStatus(404)
const package = require (`${capire}/${pkg}/package.json`) const [, scpe, pkg ] = urlRegex.exec(url)
const tarball = `capire-${pkg}-${package.version}.tgz` const package = require (`${scpe}/${pkg}/package.json`)
const tarball = `${scpe.slice(1)}-${pkg}-${package.version}.tgz`
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md // https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
res.json({ res.json({
"name": package.name, "name": package.name,
@@ -42,29 +50,30 @@ app.get('/*', (req,res)=>{
"name": package.name, "name": package.name,
"version": package.version, "version": package.version,
"dist": { "dist": {
"tarball": `http://localhost:${port}/-/${tarball}` "tarball": `${server.url}/-/${tarball}`
}, },
} }
}, },
}) })
} catch (e) { } catch (e) {
console.error(e) if (e.code === 'MODULE_NOT_FOUND') return res.sendStatus(404)
res.sendStatus(404) console.error(e); throw e
} }
}) })
app.listen(port, ()=>{ const server = app.listen(port, ()=>{
console.log (`npm set @capire:registry=http://localhost:${port}`) const url = server.url = `http://localhost:${server.address().port}`
console.log (`@capire registry listening on http://localhost:${port}`) console.log (`npm set ${scope}:registry=${url}`)
exec(`npm set @capire:registry=http://localhost:${port}`) exec(`npm set ${scope}:registry=${url}`)
console.log (`${scope} registry listening on ${url}`)
}) })
const _exit = ()=>{ const _exit = ()=>{
console.log ('\nnpm conf rm @capire:registry') server.close()
exec('npm conf rm @capire:registry') exec(`npm conf rm "${scope}:registry"`, ()=> { process.exit() })
exec('rm *.tgz')
process.exit()
} }
process.on ('SIGTERM',_exit) process.on ('SIGTERM',_exit)
process.on ('SIGHUP',_exit) process.on ('SIGHUP',_exit)
process.on ('SIGINT',_exit) process.on ('SIGINT',_exit)

View File

@@ -68,26 +68,35 @@
}, },
{ {
"file": "fiori/package.json", "file": "fiori/package.json",
"description": "#### Configuration\n\nThe `cds` section in `package.json` is a place to configure which of the `db/sqlite` and `db/hana` folders are used for which database.\nWe use [Node.js profiles](https://cap.cloud.sap/docs/node.js/cds-env#profiles) to separate the configuration.\nIn the `development` profile, you can see that `db/sqlite` is set as the model, while the `db/hana` folder is configured in the `production` profile.", "description": "#### Configuration\n\nThe `cds.requires` section in `package.json` is a place to configure which of the `db/sqlite` and `db/hana` folders are used for which database.\n\nWe use [Node.js profiles](https://cap.cloud.sap/docs/node.js/cds-env#profiles) to separate the configuration.\nIn the `development` profile, you can see that `db/sqlite` is set as the model, while the `db/hana` folder is configured in the `production` profile. `db-ext` is a pseudo datasource, its name doesn't matter.\n\nSee [`cds.resolve`](https://cap.cloud.sap/docs/node.js/cds-compile#cds-resolve) to learn more about how models are found.",
"line": 17, "selection": {
"start": {
"line": 41,
"character": 1
},
"end": {
"line": 48,
"character": 1
}
},
"title": "Configuration" "title": "Configuration"
}, },
{ {
"file": "fiori/package.json", "file": "fiori/package.json",
"description": "#### Run with SQLite\n\nTo run with `development` and an in-memory SQLite database, you don't need to do anything special, because it's activated by default. Just run:\n\n>> cds watch fiori\n\nThen open [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) to see the two new fields.\n", "description": "#### Run with SQLite\n\nTo run with `development` and an in-memory SQLite database, you don't need to do anything special, because it's activated by default. Just run:\n\n>> cds watch fiori\n\nThen open [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) to see the two new fields.\n",
"line": 28, "line": 43,
"title": "Run with SQLite" "title": "Run with SQLite"
}, },
{ {
"file": "fiori/package.json", "file": "fiori/package.json",
"description": "#### Deploy the CDS Model to SAP HANA\n\nTo 'activate' SAP HANA through the `production` profile, you can use the global `--production` flag:\n\n>> cd fiori; cds deploy --to hana --production\n\n[Learn more about SAP HANA deployment](https://cap.cloud.sap/docs/guides/databases#get-hana)\n\n#### Run the Application\n\n>> cd fiori; cds watch --production\n\nThe service on [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) is the same as before, but this time the `Authors` entity is backed by a database view with an SAP HANA function.\n\n#### More\n\nIf you don't see data, you can add some in the next step.", "description": "#### Deploy the CDS Model to SAP HANA\n\nTo 'activate' SAP HANA through the `production` profile, you can use the global `--production` flag:\n\n>> cd fiori; cds deploy --to hana --production\n\n[Learn more about SAP HANA deployment](https://cap.cloud.sap/docs/guides/databases#get-hana)\n\n#### Run the Application\n\n>> cd fiori; cds watch --production\n\nThe service on [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) is the same as before, but this time the `Authors` entity is backed by a database view with an SAP HANA function.\n\n#### More\n\nIf you don't see data, you can add some in the next step.",
"line": 31, "line": 46,
"title": "Run with SAP HANA" "title": "Run with SAP HANA"
}, },
{ {
"file": "fiori/test/requests.http", "file": "fiori/test/requests.http",
"description": "### Add More Data\n\nOptionally you can add some `Authors` data by clicking on the _Send Request_ link (provided by the [REST client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension).", "description": "### Add More Data\n\nOptionally you can add some `Authors` data by clicking on the _Send Request_ link (provided by the [REST client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension).",
"line": 68, "line": 72,
"selection": { "selection": {
"start": { "start": {
"line": 67, "line": 67,
@@ -104,6 +113,5 @@
"title": "Wrap-up", "title": "Wrap-up",
"description": "### Summary\n\nThat's it! You have seen: \n- How to integrate database-specific functions in a CDS model.\n- How to switch between the two implementations for SQLite and SAP HANA." "description": "### Summary\n\nThat's it! You have seen: \n- How to integrate database-specific functions in a CDS model.\n- How to switch between the two implementations for SQLite and SAP HANA."
} }
], ]
"ref": "main"
} }

View File

@@ -19,7 +19,7 @@
} }
}, },
{ {
"file": "hello/world.cds", "file": "hello/srv/world.cds",
"description": "### Hello World!\n\nThis is a simplistic [Hello World](https://cap.cloud.sap/docs/get-started/hello-world) service using [CDS](https://cap.cloud.sap/docs/cds/) and [cds.services](https://cap.cloud.sap/docs/node.js/api#services-api).", "description": "### Hello World!\n\nThis is a simplistic [Hello World](https://cap.cloud.sap/docs/get-started/hello-world) service using [CDS](https://cap.cloud.sap/docs/cds/) and [cds.services](https://cap.cloud.sap/docs/node.js/api#services-api).",
"line": 2, "line": 2,
"selection": { "selection": {
@@ -68,7 +68,7 @@
}, },
{ {
"file": "orders/db/schema.cds", "file": "orders/db/schema.cds",
"description": "### Compositions and Serving Documents\n\nA standalone orders management service, demonstrating:\n- Using [Compositions](https://cap.cloud.sap/docs/cds/cdl#compositions) in [Domain Models](https://cap.cloud.sap/docs/guides/domain-models), along with\n- [Serving deeply nested documents](https://cap.cloud.sap/docs/guides/generic-providers#serving-structured-data)\n", "description": "### Orders - Compositions and Serving Documents\n\nA standalone orders management service, demonstrating:\n- Using [Compositions](https://cap.cloud.sap/docs/cds/cdl#compositions) in [Domain Models](https://cap.cloud.sap/docs/guides/domain-models), along with\n- [Serving deeply nested documents](https://cap.cloud.sap/docs/guides/generic-providers#serving-structured-data)\n",
"line": 1, "line": 1,
"selection": { "selection": {
"start": { "start": {
@@ -84,7 +84,7 @@
}, },
{ {
"file": "reviews/db/schema.cds", "file": "reviews/db/schema.cds",
"description": "### More Modularity\n\nShows how to implement a modular service to manage product reviews, including:\n- Consuming other services synchronously and asynchronously\n- Serving requests synchronously\n- Emitting events asynchronously\n- Grow as you go, with:\n- Mocking app services\n- Running service meshes\n- Late-cut Micro Services\n- As well as managed data, input validations, and authorization\n", "description": "### Reviews - More Modularity\n\nShows how to implement a modular service to manage product reviews, including:\n- Consuming other services synchronously and asynchronously\n- Serving requests synchronously\n- Emitting events asynchronously\n- Grow as you go, with:\n- Mocking app services\n- Running service meshes\n- Late-cut Micro Services\n- As well as managed data, input validations, and authorization\n",
"line": 1, "line": 1,
"selection": { "selection": {
"start": { "start": {
@@ -99,8 +99,12 @@
"title": "Reviews" "title": "Reviews"
}, },
{ {
"file": "fiori/app/index.cds", "title": "Bookstore",
"description": "### Annotations for SAP Fiori Elements\n\nA [composite app, reusing and combining](https://cap.cloud.sap/docs/guides/verticalize) these packages:\n - [@capire/bookshop](bookshop)\n - [@capire/reviews](reviews)\n - [@capire/orders](orders)\n - [@capire/common](common)\n\n[Adds a SAP Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookshop, thereby introducing to:\n - [OData Annotations](https://cap.cloud.sap/docs/guides/fiori#adding-odata-annotations) in `.cds` files\n - Support for [Fiori Draft](https://cap.cloud.sap/docs/guides/fiori#draft)\n - Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help)\n - Serving SAP Fiori apps locally\n\n[The Vue.js app](bookshop/app/vue) imported from bookshop is served as well.\n", "description": "### Bookstore - Reuse and UI\n\n- A [composite app, reusing and combining](https://cap.cloud.sap/docs/guides/reuse-and-compose) these packages:\n - [@capire/bookshop](bookshop)\n - [@capire/reviews](reviews)\n - [@capire/orders](orders)\n - [@capire/common](common)\n- [The Vue.js app](bookshop/app/vue) imported from bookshop is served as well\n- [The Vue.js app](reviews/app/vue) imported from reviews is served as well\n- [The Fiori app](orders/app) imported from orders is served as well\n- [OpenAPI export + Swagger UI](https://cap.cloud.sap/docs/advanced/openapi)"
},
{
"file": "fiori/app/services.cds",
"description": "### Annotations for SAP Fiori Elements\n\n- [Adds an SAP Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookstore, thereby introducing to:\n- [OData Annotations](https://cap.cloud.sap/docs/guides/fiori#adding-odata-annotations) in `.cds` files\n- Support for [Fiori Draft](https://cap.cloud.sap/docs/guides/fiori#draft)\n- Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help)\n- Serving SAP Fiori apps locally\n",
"line": 1, "line": 1,
"selection": { "selection": {
"start": { "start": {
@@ -117,14 +121,13 @@
{ {
"file": "package.json", "file": "package.json",
"description": "### All-in-one Monorepo\n\nEach sample sub directory essentially is a standard npm package, some with standard npm dependencies to other samples. The root folder's [package.json](package.json) has local links to the sub folders, such that an `npm install` populates a local `node_modules` folder acts like a local npm registry to the individual sample packages.\n", "description": "### All-in-one Monorepo\n\nEach sample sub directory essentially is a standard npm package, some with standard npm dependencies to other samples. The root folder's [package.json](package.json) has local links to the sub folders, such that an `npm install` populates a local `node_modules` folder acts like a local npm registry to the individual sample packages.\n",
"line": 8,
"selection": { "selection": {
"start": { "start": {
"line": 8, "line": 8,
"character": 1 "character": 1
}, },
"end": { "end": {
"line": 15, "line": 16,
"character": 1 "character": 1
} }
}, },

View File

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

View File

@@ -1,7 +1,7 @@
using { Currency, managed, sap } from '@sap/cds/common'; using { Currency, managed, sap, extensible } from '@sap/cds/common';
namespace sap.capire.bookshop; namespace sap.capire.bookshop;
entity Books : managed { entity Books : managed, extensible {
key ID : Integer; key ID : Integer;
title : localized String(111); title : localized String(111);
descr : localized String(1111); descr : localized String(1111);
@@ -13,7 +13,7 @@ entity Books : managed {
image : LargeBinary @Core.MediaType : 'image/png'; image : LargeBinary @Core.MediaType : 'image/png';
} }
entity Authors : managed { entity Authors : managed, extensible {
key ID : Integer; key ID : Integer;
name : String(111); name : String(111);
dateOfBirth : Date; dateOfBirth : Date;

View File

@@ -1 +1,2 @@
exports.CatalogService = require('./srv/cat-service') const { CatalogService } = require('./srv/cat-service')
module.exports = { CatalogService }

View File

@@ -1,18 +1,18 @@
const cds = require('@sap/cds') const cds = require('@sap/cds')
const { Books } = cds.entities ('sap.capire.bookshop')
class CatalogService extends cds.ApplicationService { init(){ class CatalogService extends cds.ApplicationService { init(){
const { Books } = cds.entities ('sap.capire.bookshop')
// Reduce stock of ordered books if available stock suffices // Reduce stock of ordered books if available stock suffices
this.on ('submitOrder', async req => { this.on ('submitOrder', async req => {
const {book,quantity} = req.data const {book,quantity} = req.data
if (quantity < 1) return req.reject (400,`quantity has to be 1 or more`)
let {stock} = await SELECT `stock` .from (Books,book) let {stock} = await SELECT `stock` .from (Books,book)
if (stock >= quantity) { if (quantity > stock) return req.reject (409,`${quantity} exceeds stock for book #${book}`)
await UPDATE (Books,book) .with (`stock -=`, quantity) await UPDATE (Books,book) .with ({ stock: stock -= quantity })
await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id }) await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id })
return { stock } return { stock }
}
else return req.error (409,`${quantity} exceeds stock for book #${book}`)
}) })
// Add some discount for overstocked books // Add some discount for overstocked books

View File

@@ -32,6 +32,19 @@ GET {{server}}/admin/Authors?
# &sap-language=de # &sap-language=de
Authorization: Basic alice: Authorization: Basic alice:
### ------------------------------------------------------------------------
# Create Author
POST {{server}}/admin/Authors
Content-Type: application/json;IEEE754Compatible=true
Authorization: Basic alice:
{
"ID": 112,
"name": "Shakespeeeeere",
"age": 22
}
### ------------------------------------------------------------------------ ### ------------------------------------------------------------------------
# Create book # Create book
POST {{server}}/admin/Books POST {{server}}/admin/Books

2
bookstore/index.cds Normal file
View File

@@ -0,0 +1,2 @@
namespace sap.capire.bookshop; //> important for reflection
using from './srv/mashup';

33
bookstore/package.json Normal file
View File

@@ -0,0 +1,33 @@
{
"name": "@capire/bookstore",
"version": "1.0.0",
"dependencies": {
"@capire/bookshop": "*",
"@capire/reviews": "*",
"@capire/orders": "*",
"@capire/common": "*",
"@sap/cds": "^5",
"express": "^4.17.1"
},
"cds": {
"requires": {
"ReviewsService": {
"kind": "odata",
"model": "@capire/reviews"
},
"OrdersService": {
"kind": "odata",
"model": "@capire/orders"
},
"messaging": {
"[development]": { "kind": "file-based-messaging" },
"[hybrid]": { "kind": "enterprise-messaging-shared" },
"[production]": { "kind": "enterprise-messaging" }
},
"db": {
"kind": "sql"
}
},
"log": { "service": true }
}
}

21
bookstore/server.js Normal file
View File

@@ -0,0 +1,21 @@
const cds = require ('@sap/cds')
// Add mashup logic
cds.once('served', require('./srv/mashup'))
// Add routes to UIs from imported packages
cds.once('bootstrap',(app)=>{
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
app.serve ('/reviews') .from ('@capire/reviews','app/vue')
app.serve ('/orders') .from('@capire/orders','app/orders')
})
// Add Swagger UI
require('./srv/swagger-ui')
// Returning cds.server
module.exports = cds.server
// For didactic reasons in capire
const { ReviewsService, OrdersService } = cds.requires
if (!ReviewsService.credentials && !OrdersService.credentials) cds.requires.messaging = false

View File

@@ -1,6 +1,7 @@
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Mashing up imported models... // Enhancing bookshop with Reviews and Orders provided through
// respective reuse packages and services
// //
using { sap.capire.bookshop.Books } from '@capire/bookshop'; using { sap.capire.bookshop.Books } from '@capire/bookshop';
@@ -8,18 +9,24 @@ using { sap.capire.bookshop.Books } from '@capire/bookshop';
// //
// Extend Books with access to Reviews and average ratings // Extend Books with access to Reviews and average ratings
// //
using { ReviewsService.Reviews } from '@capire/reviews'; using { ReviewsService.Reviews } from '@capire/reviews';
extend Books with { extend Books with {
reviews : Composition of many Reviews on reviews.subject = $self.ID; reviews : Composition of many Reviews on reviews.subject = $self.ID;
rating : Decimal; rating : Decimal;
numberOfReviews : Integer;
} }
// //
// Extend Orders with Books as Products // Extend Orders with Books as Products
// //
using { sap.capire.orders.Orders } from '@capire/orders';
using { sap.capire.orders.Orders_Items } from '@capire/orders'; extend Orders with {
extend Orders_Items with { extend Items with {
book : Association to Books on product.ID = book.ID book : Association to Books on product.ID = book.ID
}
} }
// Add orders fiori app (in case of embedded orders service)
using from '@capire/orders/app/fiori';

View File

@@ -1,6 +1,6 @@
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Mashing up provided and required services... // Mashing up bookshop services with required services...
// //
module.exports = async()=>{ // called by server.js module.exports = async()=>{ // called by server.js
@@ -20,7 +20,7 @@ module.exports = async()=>{ // called by server.js
CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => { CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => {
console.debug ('> delegating request to ReviewsService') console.debug ('> delegating request to ReviewsService')
const [id] = req.params, { columns, limit } = req.query.SELECT const [id] = req.params, { columns, limit } = req.query.SELECT
return ReviewsService.tx(req).read ('Reviews',columns).limit(limit).where({subject:String(id)}) return ReviewsService.read ('Reviews',columns).limit(limit).where({subject:String(id)})
})) }))
// //
@@ -37,13 +37,12 @@ module.exports = async()=>{ // called by server.js
}) })
// //
// Update Books' average ratings when ReviewsService signals updatd reviews // Update Books' average ratings when ReviewsService signals updated reviews
// //
ReviewsService.on ('reviewed', (msg) => { ReviewsService.on ('reviewed', (msg) => {
console.debug ('> received:', msg.event, msg.data) console.debug ('> received:', msg.event, msg.data)
const { subject, rating } = msg.data const { subject, count, rating } = msg.data
return UPDATE(Books,subject).with({rating}) return UPDATE(Books,subject).with({ numberOfReviews:count, rating })
// ^ Note: the framework will execute this and take care for db.tx
}) })
// //

View File

@@ -0,0 +1,10 @@
// -----------------------------------------------------------------------
// Adding Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
const cds = require ('@sap/cds')
try {
const cds_swagger = require ('cds-swagger-ui-express')
cds.once ('bootstrap', app => app.use (cds_swagger()) )
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') throw err
}

View File

@@ -2,7 +2,7 @@
@bookshop = http://localhost:4004 @bookshop = http://localhost:4004
@reviews-service = {{bookshop}}/reviews @reviews-service = {{bookshop}}/reviews
# Uncomment this when running a separate reviews service # Uncomment this when running a separate reviews service
@reviews-service = http://localhost:4005/reviews # @reviews-service = http://localhost:4005/reviews

View File

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

View File

@@ -0,0 +1,47 @@
using {AdminService} from '@capire/bookshop';
annotate AdminService.Authors with @odata.draft.enabled;
////////////////////////////////////////////////////////////////////////////
//
// Authors Object Page
//
annotate AdminService.Authors with @(UI : {
HeaderInfo : {
TypeName : 'Author',
TypeNamePlural : 'Authors',
Description : {Value : lifetime}
},
Facets : [
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Details}',
Target : '@UI.FieldGroup#Details'
},
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Books}',
Target : 'books/@UI.LineItem'
},
],
FieldGroup #Details : {Data : [
{Value : placeOfBirth},
{Value : placeOfDeath},
{Value : dateOfBirth},
{Value : dateOfDeath},
{
Value : age,
Label : '{i18n>Age}'
},
]},
});
// Workaround to avoid errors for unknown db-specific calculated fields above
extend sap.capire.bookshop.Authors with {
virtual age : Integer;
virtual lifetime : String;
}
// Workaround for Fiori popup for asking user to enter a new UUID on Create
annotate AdminService.Authors with { ID @Core.Computed; }

View File

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

View File

@@ -0,0 +1,11 @@
# This is the resource bundle of itelo
# __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459
# JCI app descriptor contains lower case TITLE
appTitle=Bookshop Authors
# JCI app descriptor contains lower case DESCRIPTION
appSubTitle=Bookshop Authors
# JCI app descriptor contains lower case DESCRIPTION
appDescription=Bookshop Authors

View File

@@ -0,0 +1,145 @@
{
"_version": "1.28.0",
"sap.app": {
"id": "authors",
"type": "application",
"title": "Manage Authors",
"description": "Sample Application",
"i18n": "i18n/i18n.properties",
"applicationVersion": {
"version": "1.0.0"
},
"dataSources": {
"AdminService": {
"uri": "/admin/",
"type": "OData",
"settings": {
"odataVersion": "4.0"
}
}
},
"sourceTemplate": {
"id": "ui5template.basicSAPUI5ApplicationProject",
"-id": "ui5template.smartTemplate",
"version": "1.40.12"
},
"crossNavigation": {
"inbounds": {
"intent1": {
"signature": {
"parameters": {
"Books.author.ID":{
"renameTo": "ID"
}
},
"additionalParameters": "ignored"
},
"semanticObject": "Authors",
"action": "display",
"title": "{{appTitle}}",
"info": "{{appInfo}}",
"subTitle": "{{appSubTitle}}",
"icon": "sap-icon://SAP-icons-TNT/user",
"indicatorDataSource": {
"dataSource": "AdminService",
"path": "Authors/$count",
"refresh": 1800
}
}
}
}
},
"sap.ui5": {
"flexEnabled": true,
"config": {
"experimentalCAPScenario": true
},
"dependencies": {
"minUI5Version": "1.81.0",
"libs": {
"sap.fe.templates": {}
}
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"uri": "i18n/i18n.properties"
},
"": {
"dataSource": "AdminService",
"settings": {
"synchronizationMode": "None",
"operationMode": "Server",
"autoExpandSelect": true,
"earlyRequests": true,
"groupProperties": {
"default": {
"submit": "Auto"
}
}
}
}
},
"routing": {
"routes": [
{
"pattern": ":?query:",
"name": "AuthorsList",
"target": "AuthorsList"
},
{
"pattern": "Authors({key}):?query:",
"name": "AuthorsDetails",
"target": "AuthorsDetails"
}
],
"targets": {
"AuthorsList": {
"type": "Component",
"id": "AuthorsList",
"name": "sap.fe.templates.ListReport",
"options": {
"settings": {
"entitySet": "Authors",
"initialLoad": true,
"navigation": {
"Authors": {
"detail": {
"route": "AuthorsDetails"
}
}
}
}
}
},
"AuthorsDetails": {
"type": "Component",
"id": "AuthorsDetailsList",
"name": "sap.fe.templates.ObjectPage",
"options": {
"settings": {
"entitySet": "Authors"
}
}
}
}
},
"contentDensities": {
"compact": true,
"cozy": true
}
},
"sap.ui": {
"technology": "UI5",
"fullWidth": false,
"deviceTypes":{
"desktop": true,
"tablet": true,
"phone": true
}
},
"sap.fiori": {
"registrationIds": [],
"archeType": "transactional"
}
}

View File

@@ -1,4 +1,4 @@
using { AdminService } from '../../db/schema'; using { AdminService } from '@capire/bookstore';
using from '../common'; // to help UI linter get the complete annotations using from '../common'; // to help UI linter get the complete annotations
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -40,27 +40,6 @@ annotate AdminService.Books with @(
} }
); );
annotate AdminService.Authors with @(
UI: {
HeaderInfo: {
Description: {Value: lifetime}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Books}', Target: 'books/@UI.LineItem'},
],
FieldGroup#Details: {
Data: [
{Value: placeOfBirth},
{Value: placeOfDeath},
{Value: dateOfBirth},
{Value: dateOfDeath},
{Value: age, Label: '{i18n>Age}'},
]
},
}
);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
@@ -85,10 +64,14 @@ annotate AdminService.Books.texts with @(
// Add Value Help for Locales // Add Value Help for Locales
annotate AdminService.Books.texts { annotate AdminService.Books.texts {
locale @ValueList:{entity:'Languages',type:#fixed} locale @ValueList:{entity:'Languages'};
locale @Common.ValueListWithFixedValues:true; //show as drop down, not a dialog
} }
// In addition we need to expose Languages through AdminService as a target for ValueList // In addition we need to expose Languages through AdminService as a target for ValueList
using { sap } from '@sap/cds/common'; using { sap } from '@sap/cds/common';
extend service AdminService { extend service AdminService {
@readonly entity Languages as projection on sap.common.Languages; @readonly entity Languages as projection on sap.common.Languages;
} }
// Workaround for Fiori popup for asking user to enter a new UUID on Create
annotate AdminService.Books with { ID @Core.Computed; }

View File

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

View File

@@ -1,7 +1,7 @@
{ {
"_version": "1.8.0", "_version": "1.8.0",
"sap.app": { "sap.app": {
"id": "admin", "id": "books",
"type": "application", "type": "application",
"title": "Manage Books", "title": "Manage Books",
"description": "Sample Application", "description": "Sample Application",
@@ -22,6 +22,10 @@
} }
}, },
"sap.ui5": { "sap.ui5": {
"flexEnabled": true,
"config": {
"experimentalCAPScenario": true
},
"dependencies": { "dependencies": {
"libs": { "libs": {
"sap.fe.templates": {} "sap.fe.templates": {}
@@ -73,6 +77,7 @@
"options": { "options": {
"settings" : { "settings" : {
"entitySet" : "Books", "entitySet" : "Books",
"initialLoad": true,
"navigation" : { "navigation" : {
"Books" : { "Books" : {
"detail" : { "detail" : {

View File

@@ -0,0 +1,142 @@
{
"services": {
"LaunchPage": {
"adapter": {
"config": {
"catalogs": [],
"groups": [
{
"id": "Bookshop",
"title": "Bookshop",
"isPreset": true,
"isVisible": true,
"isGroupLocked": false,
"tiles": [
{
"id": "BrowseBooks",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Browse Books",
"targetURL": "#Books-display"
}
}
]
},
{
"id": "Administration",
"title": "Administration",
"isPreset": true,
"isVisible": true,
"isGroupLocked": false,
"tiles": [
{
"id": "ManageBooks",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Manage Books",
"targetURL": "#Books-manage"
}
},
{
"id": "ManageAuthors",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Manage Authors",
"targetURL": "#Authors-display"
}
},
{
"id": "ManageOrders",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Manage Orders",
"targetURL": "#Orders-manage"
}
}
]
}
]
}
}
},
"NavTargetResolution": {
"config": {
"enableClientSideTargetResolution": true
}
},
"ClientSideTargetResolution": {
"adapter": {
"config": {
"inbounds": {
"BrowseBooks": {
"semanticObject": "Books",
"action": "display",
"title": "Browse Books",
"signature": {
"parameters": {
"Books.ID": {
"renameTo": "ID"
},
"Authors.books.ID": {
"renameTo": "ID"
}
},
"additionalParameters": "ignored"
},
"resolutionResult": {
"applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=bookshop",
"url": "/browse/webapp"
}
},
"BrowseAuthors": {
"semanticObject": "Authors",
"action": "display",
"title": "Browse Authors",
"signature": {
"parameters": {
"Books.author.ID":{
"renameTo": "ID"
}
},
"additionalParameters": "ignored"
},
"resolutionResult": {
"applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=authors",
"url": "/admin-authors/webapp"
}
},
"ManageBooks": {
"semanticObject": "Books",
"action": "manage",
"title": "Manage Books",
"signature": {
"parameters": {},
"additionalParameters": "allowed"
},
"resolutionResult": {
"applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=books",
"url": "/admin-books/webapp"
}
},
"ManageOrders": {
"semanticObject": "Orders",
"action": "manage",
"signature": {
"parameters": {},
"additionalParameters": "allowed"
},
"resolutionResult": {
"applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=orders",
"url": "/orders/webapp"
}
}
}
}
}
}
}
}

View File

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

View File

@@ -1,50 +1,60 @@
using CatalogService from '@capire/bookshop'; using CatalogService from '@capire/bookstore';
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Books Object Page // Books Object Page
// //
annotate CatalogService.Books with @( annotate CatalogService.Books with @(UI : {
UI: { HeaderInfo : {
HeaderInfo: { TypeName : 'Book',
TypeName: 'Book', TypeNamePlural : 'Books',
TypeNamePlural: 'Books', Description : {Value : author}
Description: {Value: author}
}, },
HeaderFacets: [ HeaderFacets : [{
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Description}', Target: '@UI.FieldGroup#Descr'}, $Type : 'UI.ReferenceFacet',
], Label : '{i18n>Description}',
Facets: [ Target : '@UI.FieldGroup#Descr'
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Price'}, }, ],
], Facets : [{
FieldGroup#Descr: { $Type : 'UI.ReferenceFacet',
Data: [ Label : '{i18n>Details}',
{Value: descr}, Target : '@UI.FieldGroup#Price'
] }, ],
FieldGroup #Descr : {Data : [{Value : descr}, ]},
FieldGroup #Price : {Data : [
{Value : price},
{
Value : currency.symbol,
Label : '{i18n>Currency}'
}, },
FieldGroup#Price: { ]},
Data: [ });
{Value: price},
{Value: currency.symbol, Label: '{i18n>Currency}'},
]
},
}
);
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Books Object Page // Books Object Page
// //
annotate CatalogService.Books with @( annotate CatalogService.Books with @(UI : {
UI: { SelectionFields : [
SelectionFields: [ ID, price, currency_code ], ID,
LineItem: [ price,
{Value: title}, currency_code
{Value: author, Label:'{i18n>Author}'}, ],
{Value: genre.name}, LineItem : [
{Value: price}, {
{Value: currency.symbol, Label:' '}, Value : ID,
] Label : '{i18n>Title}'
}, },
); {
Value : author,
Label : '{i18n>Author}'
},
{Value : genre.name},
{Value : price},
{
Value : currency.symbol,
Label : ' '
},
]
}, );

View File

@@ -1,11 +1,14 @@
{ {
"_version": "1.8.0", "_version": "1.28.0",
"sap.app": { "sap.app": {
"id": "bookshop", "id": "bookshop",
"type": "application", "type": "application",
"title": "Browse Books", "title": "Browse Books",
"description": "Sample Application", "description": "Sample Application",
"i18n": "i18n/i18n.properties", "i18n": "i18n/i18n.properties",
"applicationVersion": {
"version": "1.0.0"
},
"dataSources": { "dataSources": {
"CatalogService": { "CatalogService": {
"uri": "/browse/", "uri": "/browse/",
@@ -15,14 +18,43 @@
} }
} }
}, },
"-sourceTemplate": { "sourceTemplate": {
"id": "ui5template.basicSAPUI5ApplicationProject", "id": "ui5template.basicSAPUI5ApplicationProject",
"-id": "ui5template.smartTemplate", "-id": "ui5template.smartTemplate",
"-version": "1.40.12" "version": "1.40.12"
},
"crossNavigation": {
"inbounds": {
"intent1": {
"signature": {
"parameters": {
"Books.ID":{
"renameTo": "ID"
},
"Authors.books.ID": {
"renameTo": "ID"
}
},
"additionalParameters": "ignored"
},
"semanticObject": "Books",
"action": "display",
"title": "{{appTitle}}",
"info": "{{appInfo}}",
"subTitle": "{{appSubTitle}}",
"icon": "sap-icon://course-book",
"indicatorDataSource": {
"dataSource": "CatalogService",
"path": "Books/$count",
"refresh": 1800
}
}
}
} }
}, },
"sap.ui5": { "sap.ui5": {
"dependencies": { "dependencies": {
"minUI5Version": "1.81.0",
"libs": { "libs": {
"sap.fe.templates": {} "sap.fe.templates": {}
} }
@@ -68,6 +100,7 @@
"options": { "options": {
"settings": { "settings": {
"entitySet": "Books", "entitySet": "Books",
"initialLoad": true,
"navigation": { "navigation": {
"Books": { "Books": {
"detail": { "detail": {
@@ -97,7 +130,12 @@
}, },
"sap.ui": { "sap.ui": {
"technology": "UI5", "technology": "UI5",
"fullWidth": false "fullWidth": false,
"deviceTypes":{
"desktop": true,
"tablet": true,
"phone": true
}
}, },
"sap.fiori": { "sap.fiori": {
"registrationIds": [], "registrationIds": [],

View File

@@ -2,7 +2,7 @@
Common Annotations shared by all apps Common Annotations shared by all apps
*/ */
using { sap.capire.bookshop as my } from '@capire/bookshop'; using { sap.capire.bookshop as my } from '@capire/bookstore';
using { sap.common } from '@capire/common'; using { sap.common } from '@capire/common';
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -10,39 +10,52 @@ using { sap.common } from '@capire/common';
// Books Lists // Books Lists
// //
annotate my.Books with @( annotate my.Books with @(
Common.SemanticKey: [title], Common.SemanticKey : [ID],
UI: { UI : {
Identification: [{Value:title}], Identification : [{Value : title}],
SelectionFields: [ ID, author_ID, price, currency_code ], SelectionFields : [
LineItem: [ ID,
{Value: ID}, author_ID,
{Value: title}, price,
{Value: author.name, Label:'{i18n>Author}'}, currency_code
{Value: genre.name}, ],
{Value: stock}, LineItem : [
{Value: price}, {
{Value: currency.symbol, Label:' '}, Value : ID,
Label : '{i18n>Title}'
},
{
Value : author.ID,
Label : '{i18n>Author}'
},
{Value : genre.name},
{Value : stock},
{Value : price},
{
Value : currency.symbol,
Label : ' '
},
] ]
} }
) { ) {
author @ValueList.entity:'Authors'; ID @Common: {
SemanticObject : 'Books',
Text: title,
TextArrangement : #TextOnly
};
author @ValueList.entity : 'Authors';
}; };
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Books Details // Books Details
// //
annotate my.Books with @( annotate my.Books with @(UI : {HeaderInfo : {
UI: { TypeName : '{i18n>Book}',
HeaderInfo: { TypeNamePlural : '{i18n>Books}',
TypeName: '{i18n>Book}', Title : {Value : title},
TypeNamePlural: '{i18n>Books}', Description : {Value : author.name}
Title: {Value: title}, }, });
Description: {Value: author.name}
},
}
);
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -50,12 +63,18 @@ annotate my.Books with @(
// Books Elements // Books Elements
// //
annotate my.Books with { annotate my.Books with {
ID @title:'{i18n>ID}' @UI.HiddenFilter; ID @title : '{i18n>ID}';
title @title:'{i18n>Title}'; title @title : '{i18n>Title}';
genre @title:'{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly }; genre @title : '{i18n>Genre}' @Common : {
author @title:'{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly }; Text : genre.name,
price @title:'{i18n>Price}' @Measures.ISOCurrency: currency_code; TextArrangement : #TextOnly
stock @title:'{i18n>Stock}'; };
author @title : '{i18n>Author}' @Common : {
Text : author.name,
TextArrangement : #TextOnly
};
price @title : '{i18n>Price}' @Measures.ISOCurrency : currency_code;
stock @title : '{i18n>Stock}';
descr @UI.MultiLineText; descr @UI.MultiLineText;
} }
@@ -64,12 +83,15 @@ annotate my.Books with {
// Genres List // Genres List
// //
annotate my.Genres with @( annotate my.Genres with @(
Common.SemanticKey: [name], Common.SemanticKey : [name],
UI: { UI : {
SelectionFields: [ name ], SelectionFields : [name],
LineItem:[ LineItem : [
{Value: name}, {Value : name},
{Value: parent.name, Label: 'Main Genre'}, {
Value : parent.name,
Label : 'Main Genre'
},
], ],
} }
); );
@@ -78,28 +100,28 @@ annotate my.Genres with @(
// //
// Genre Details // Genre Details
// //
annotate my.Genres with @( annotate my.Genres with @(UI : {
UI: { Identification : [{Value : name}],
Identification: [{Value:name}], HeaderInfo : {
HeaderInfo: { TypeName : '{i18n>Genre}',
TypeName: '{i18n>Genre}', TypeNamePlural : '{i18n>Genres}',
TypeNamePlural: '{i18n>Genres}', Title : {Value : name},
Title: {Value: name}, Description : {Value : ID}
Description: {Value: ID}
}, },
Facets: [ Facets : [{
{$Type: 'UI.ReferenceFacet', Label: '{i18n>SubGenres}', Target: 'children/@UI.LineItem'}, $Type : 'UI.ReferenceFacet',
], Label : '{i18n>SubGenres}',
} Target : 'children/@UI.LineItem'
); }, ],
});
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Genres Elements // Genres Elements
// //
annotate my.Genres with { annotate my.Genres with {
ID @title: '{i18n>ID}'; ID @title : '{i18n>ID}';
name @title: '{i18n>Genre}'; name @title : '{i18n>Genre}';
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -107,38 +129,42 @@ annotate my.Genres with {
// Authors List // Authors List
// //
annotate my.Authors with @( annotate my.Authors with @(
Common.SemanticKey: [name], Common.SemanticKey : [ID],
UI: { UI : {
Identification: [{Value:name}], Identification : [{Value : name}],
SelectionFields: [ name ], SelectionFields : [name],
LineItem:[ LineItem : [
{Value: ID}, {Value : ID},
{Value: name}, {Value : dateOfBirth},
{Value: dateOfBirth}, {Value : dateOfDeath},
{Value: dateOfDeath}, {Value : placeOfBirth},
{Value: placeOfBirth}, {Value : placeOfDeath},
{Value: placeOfDeath},
], ],
} }
); ) {
ID @Common: {
SemanticObject : 'Authors',
Text: name,
TextArrangement : #TextOnly,
};
};
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Author Details // Author Details
// //
annotate my.Authors with @( annotate my.Authors with @(UI : {
UI: { HeaderInfo : {
HeaderInfo: { TypeName : '{i18n>Author}',
TypeName: '{i18n>Author}', TypeNamePlural : '{i18n>Authors}',
TypeNamePlural: '{i18n>Authors}', Title : {Value : name},
Title: {Value: name}, Description : {Value : dateOfBirth}
Description: {Value: dateOfBirth}
}, },
Facets: [ Facets : [{
{$Type: 'UI.ReferenceFacet', Target: 'books/@UI.LineItem'}, $Type : 'UI.ReferenceFacet',
], Target : 'books/@UI.LineItem'
} }, ],
); });
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -146,12 +172,12 @@ annotate my.Authors with @(
// Authors Elements // Authors Elements
// //
annotate my.Authors with { annotate my.Authors with {
ID @title:'{i18n>ID}' @UI.HiddenFilter; ID @title : '{i18n>ID}';
name @title:'{i18n>Name}'; name @title : '{i18n>Name}';
dateOfBirth @title:'{i18n>DateOfBirth}'; dateOfBirth @title : '{i18n>DateOfBirth}';
dateOfDeath @title:'{i18n>DateOfDeath}'; dateOfDeath @title : '{i18n>DateOfDeath}';
placeOfBirth @title:'{i18n>PlaceOfBirth}'; placeOfBirth @title : '{i18n>PlaceOfBirth}';
placeOfDeath @title:'{i18n>PlaceOfDeath}'; placeOfDeath @title : '{i18n>PlaceOfDeath}';
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
@@ -159,13 +185,16 @@ annotate my.Authors with {
// Languages List // Languages List
// //
annotate common.Languages with @( annotate common.Languages with @(
Common.SemanticKey: [code], Common.SemanticKey : [code],
Identification: [{Value:code}], Identification : [{Value : code}],
UI: { UI : {
SelectionFields: [ name, descr ], SelectionFields : [
LineItem:[ name,
{Value: code}, descr
{Value: name}, ],
LineItem : [
{Value : code},
{Value : name},
], ],
} }
); );
@@ -174,40 +203,41 @@ annotate common.Languages with @(
// //
// Language Details // Language Details
// //
annotate common.Languages with @( annotate common.Languages with @(UI : {
UI: { HeaderInfo : {
HeaderInfo: { TypeName : '{i18n>Language}',
TypeName: '{i18n>Language}', TypeNamePlural : '{i18n>Languages}',
TypeNamePlural: '{i18n>Languages}', Title : {Value : name},
Title: {Value: name}, Description : {Value : descr}
Description: {Value: descr}
}, },
Facets: [ Facets : [{
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'}, $Type : 'UI.ReferenceFacet',
], Label : '{i18n>Details}',
FieldGroup#Details: { Target : '@UI.FieldGroup#Details'
Data: [ }, ],
{Value: code}, FieldGroup #Details : {Data : [
{Value: name}, {Value : code},
{Value: descr} {Value : name},
] {Value : descr}
}, ]},
} });
);
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Currencies List // Currencies List
// //
annotate common.Currencies with @( annotate common.Currencies with @(
Common.SemanticKey: [code], Common.SemanticKey : [code],
Identification: [{Value:code}], Identification : [{Value : code}],
UI: { UI : {
SelectionFields: [ name, descr ], SelectionFields : [
LineItem:[ name,
{Value: descr}, descr
{Value: symbol}, ],
{Value: code}, LineItem : [
{Value : descr},
{Value : symbol},
{Value : code},
], ],
} }
); );
@@ -216,42 +246,44 @@ annotate common.Currencies with @(
// //
// Currency Details // Currency Details
// //
annotate common.Currencies with @( annotate common.Currencies with @(UI : {
UI: { HeaderInfo : {
HeaderInfo: { TypeName : '{i18n>Currency}',
TypeName: '{i18n>Currency}', TypeNamePlural : '{i18n>Currencies}',
TypeNamePlural: '{i18n>Currencies}', Title : {Value : descr},
Title: {Value: descr}, Description : {Value : code}
Description: {Value: code} },
Facets : [
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Details}',
Target : '@UI.FieldGroup#Details'
},
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Extended}',
Target : '@UI.FieldGroup#Extended'
}, },
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Extended}', Target: '@UI.FieldGroup#Extended'},
], ],
FieldGroup#Details: { FieldGroup #Details : {Data : [
Data: [ {Value : name},
{Value: name}, {Value : symbol},
{Value: symbol}, {Value : code},
{Value: code}, {Value : descr}
{Value: descr} ]},
] FieldGroup #Extended : {Data : [
}, {Value : numcode},
FieldGroup#Extended: { {Value : minor},
Data: [ {Value : exponent}
{Value: numcode}, ]},
{Value: minor}, });
{Value: exponent}
]
},
}
);
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Currencies Elements // Currencies Elements
// //
annotate common.Currencies with { annotate common.Currencies with {
numcode @title:'{i18n>NumCode}'; numcode @title : '{i18n>NumCode}';
minor @title:'{i18n>MinorUnit}'; minor @title : '{i18n>MinorUnit}';
exponent @title:'{i18n>Exponent}'; exponent @title : '{i18n>Exponent}';
} }

View File

@@ -10,31 +10,20 @@
<script> <script>
window["sap-ushell-config"] = { window["sap-ushell-config"] = {
defaultRenderer: "fiori2", defaultRenderer: "fiori2",
applications: { applications: {},
"browse-books": { bootstrapPlugins: {
title: "Browse Books", RuntimeAuthoringPlugin: {
description: "w/ SAP Fiori Elements", component: "sap.ushell.plugins.rta",
additionalInformation: "SAPUI5.Component=bookshop", config: {
applicationType : "URL", validateAppVersion: false,
url: "/browse/webapp", },
navigationMode: "embedded" },
PersonalizePlugin: {
component: "sap.ushell.plugins.rta-personalize",
config: {
validateAppVersion: false,
}, },
"manage-books": {
title: "Manage Books",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=admin",
applicationType : "URL",
url: "/admin/webapp",
navigationMode: "embedded"
}, },
"manage-orders": {
title: "Manage Orders",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=orders",
applicationType : "URL",
url: "/orders/webapp",
navigationMode: "embedded"
}
} }
}; };
</script> </script>
@@ -47,8 +36,13 @@
data-sap-ui-frameOptions="allow" data-sap-ui-frameOptions="allow"
></script> ></script>
<script> <script>
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content")) sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"));
</script> sap.ui
.getCore()
.getConfiguration()
.setFlexibilityServices([{ connector: "SessionStorageConnector" }]);
sap.ui.getCore().getConfiguration().setLanguage("en");
</script>
</head> </head>
<body class="sapUiBody" id="content"></body> <body class="sapUiBody" id="content"></body>

View File

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

View File

@@ -2,11 +2,8 @@
This model controls what gets served to Fiori frontends... This model controls what gets served to Fiori frontends...
*/ */
using from './admin/fiori-service'; using from './admin-authors/fiori-service';
using from './admin-books/fiori-service';
using from './browse/fiori-service'; using from './browse/fiori-service';
using from './common'; using from './common';
using from '@capire/bookstore/srv/mashup';
using from '@capire/common';
// only works in case of embedded orders service
using from '@capire/orders/app/orders/fiori-service';

View File

@@ -2,7 +2,7 @@
// Add Author.age and .lifetime with a DB-specific function // Add Author.age and .lifetime with a DB-specific function
// //
using { AdminService } from '../schema'; using { AdminService } from '@capire/bookshop';
extend projection AdminService.Authors with { extend projection AdminService.Authors with {
YEARS_BETWEEN(dateOfBirth, dateOfDeath) as age: Integer, YEARS_BETWEEN(dateOfBirth, dateOfDeath) as age: Integer,

View File

@@ -1,8 +0,0 @@
using { sap.capire.bookshop } from '@capire/bookshop';
// Forward-declare calculated fields to be filled in database-specific ways
// TODO find a better way to have 'default' fields that still can be overwritten.
extend bookshop.Authors with {
virtual age: Integer;
virtual lifetime: String;
}

View File

@@ -2,7 +2,7 @@
// Add Author.age and .lifetime with a DB-specific function // Add Author.age and .lifetime with a DB-specific function
// //
using { AdminService } from '../schema'; using { AdminService } from '@capire/bookshop';
extend projection AdminService.Authors with { extend projection AdminService.Authors with {
strftime('%Y',dateOfDeath)-strftime('%Y',dateOfBirth) as age: Integer, strftime('%Y',dateOfDeath)-strftime('%Y',dateOfBirth) as age: Integer,

View File

@@ -2,10 +2,7 @@
"name": "@capire/fiori", "name": "@capire/fiori",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@capire/bookshop": "*", "@capire/bookstore": "*",
"@capire/reviews": "*",
"@capire/orders": "*",
"@capire/common": "*",
"@sap/cds": "^5", "@sap/cds": "^5",
"express": "^4.17.1", "express": "^4.17.1",
"passport": "^0.4.1" "passport": "^0.4.1"
@@ -15,10 +12,10 @@
"watch": "cds watch" "watch": "cds watch"
}, },
"cds": { "cds": {
"hana": {
"deploy-format": "hdbtable"
},
"requires": { "requires": {
"extensibility": {
"kind": "uiflex"
},
"auth": { "auth": {
"strategy": "dummy" "strategy": "dummy"
}, },
@@ -30,14 +27,30 @@
"kind": "odata", "kind": "odata",
"model": "@capire/orders" "model": "@capire/orders"
}, },
"messaging": {
"[production]": {
"kind": "enterprise-messaging"
},
"[development]": {
"kind": "file-based-messaging"
},
"[hybrid!]": {
"kind": "enterprise-messaging-shared"
}
},
"db": { "db": {
"kind": "sql", "kind": "sql"
},
"db-ext": {
"[development]": { "[development]": {
"model": "db/sqlite" "model": "db/sqlite"
}, },
"[production]": { "[production]": {
"model": "db/hana" "model": "db/hana"
} }
},
"hana": {
"deploy-format": "hdbtable"
} }
} }
} }

View File

@@ -1,25 +1 @@
const cds = require ('@sap/cds') module.exports = require('@capire/bookstore/server.js')
module.exports = cds.server
cds.once('bootstrap',(app)=>{
app.use ('/orders/webapp', _from('@capire/orders/app/orders/webapp/manifest.json'))
app.use ('/bookshop', _from('@capire/bookshop/app/vue/index.html'))
app.use ('/reviews', _from('@capire/reviews/app/vue/index.html'))
})
cds.once('served', require('./srv/mashup'))
// Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
try {
const cds_swagger = require ('cds-swagger-ui-express')
cds.once ('bootstrap', app => app.use (cds_swagger()) )
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') throw err
}
// -----------------------------------------------------------------------
// Helper for serving static content from npm-installed packages
const {static} = require('express')
const {dirname} = require('path')
const _from = target => static (dirname (require.resolve(target)))

View File

@@ -7,8 +7,8 @@
"start:ts": "cds-ts serve srv/world.cds" "start:ts": "cds-ts serve srv/world.cds"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^26.0.23", "@types/jest": "^27.0.2",
"@types/node": "^15.12.0", "@types/node": "^16.11.6",
"ts-jest": "^27.0.2", "ts-jest": "^27.0.2",
"typescript": "^4.3.5" "typescript": "^4.3.5"
}, },

View File

@@ -1,5 +1,7 @@
import type { Request } from "@sap/cds/apis/services"
module.exports = class say { module.exports = class say {
hello(req: any) { hello(req: Request) {
return `Hello ${req.data.to} from a TypeScript file!` return `Hello ${req.data.to} from a TypeScript file!`
} }
} }

View File

@@ -10,7 +10,7 @@
using { OrdersService } from '../../srv/orders-service'; using { OrdersService } from '../srv/orders-service';
@odata.draft.enabled @odata.draft.enabled
@@ -68,7 +68,7 @@ annotate OrdersService.Orders with @(
annotate OrdersService.Orders_Items with @( annotate OrdersService.Orders.Items with @(
UI: { UI: {
LineItem: [ LineItem: [
{Value: product_ID, Label:'Product ID'}, {Value: product_ID, Label:'Product ID'},

View File

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

View File

Internal Server Error - Bruno's Gitea
500 Internal Server Error

Gitea Version: 1.24.4