Compare commits
99 Commits
experiment
...
d034457-UI
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e08cdf7831 | ||
|
|
1ec44aaecf | ||
|
|
a6ea2fb130 | ||
|
|
1fde0c928b | ||
|
|
fa854a2411 | ||
|
|
40b6acee6a | ||
|
|
a37f1b8e0c | ||
|
|
042ddec41c | ||
|
|
8d8aa6c2c9 | ||
|
|
756ed48ce3 | ||
|
|
19b680ab3d | ||
|
|
33380c0792 | ||
|
|
0d5b65d93f | ||
|
|
dcf5ec6068 | ||
|
|
fb41dd100b | ||
|
|
769aa2b4f5 | ||
|
|
f6c5e620c0 | ||
|
|
7013cb6296 | ||
|
|
6411ce4ea3 | ||
|
|
cd914a8d87 | ||
|
|
b35e92b5bc | ||
|
|
8350aff111 | ||
|
|
9aa604c5a0 | ||
|
|
f5cb53adf8 | ||
|
|
9fe16edc55 | ||
|
|
0093883f25 | ||
|
|
14024a4837 | ||
|
|
2e13b37096 | ||
|
|
7407b0aae3 | ||
|
|
152d088ff4 | ||
|
|
19c886d6dc | ||
|
|
575cffe70a | ||
|
|
03869e5896 | ||
|
|
597afeb3d8 | ||
|
|
d376e6e063 | ||
|
|
2a44cfa9dc | ||
|
|
7964c9aefd | ||
|
|
ce752097d0 | ||
|
|
e838f380c5 | ||
|
|
e89e012eba | ||
|
|
bf0f57efe5 | ||
|
|
40ea057f7f | ||
|
|
31d2f4c932 | ||
|
|
8cc2db7118 | ||
|
|
482b71e60b | ||
|
|
7536451e29 | ||
|
|
2703e8b999 | ||
|
|
ac2e4dd6f9 | ||
|
|
c718fa400d | ||
|
|
730da6e435 | ||
|
|
7d9b4f7a5d | ||
|
|
35d3708592 | ||
|
|
eaaf0d29a5 | ||
|
|
64fe700d1e | ||
|
|
5c3cec973e | ||
|
|
680a6ae68f | ||
|
|
366b0f8f9a | ||
|
|
b95df77b9a | ||
|
|
7f6b87171a | ||
|
|
21e74bbbfb | ||
|
|
102b15c3cd | ||
|
|
73db2e96bc | ||
|
|
7bb58ee2d5 | ||
|
|
27e82d16e0 | ||
|
|
ff9bbe6d8d | ||
|
|
5f1b7b8cbf | ||
|
|
0253300557 | ||
|
|
139d957495 | ||
|
|
404427237b | ||
|
|
5f89334403 | ||
|
|
e612fa97ea | ||
|
|
da2ea39466 | ||
|
|
117000df71 | ||
|
|
f3ffb69d3a | ||
|
|
f908484973 | ||
|
|
c4529f3cd7 | ||
|
|
0220400484 | ||
|
|
c1911b6e96 | ||
|
|
19083d156e | ||
|
|
48d547e6cd | ||
|
|
bae491a832 | ||
|
|
ca41a2141c | ||
|
|
08f409af73 | ||
|
|
efa60550fb | ||
|
|
f599206bf4 | ||
|
|
2f5d159428 | ||
|
|
2be3d50389 | ||
|
|
46b58f1b5c | ||
|
|
e87527cbcd | ||
|
|
091844219b | ||
|
|
de796e5a89 | ||
|
|
f988088412 | ||
|
|
c4a51ab719 | ||
|
|
f58376607a | ||
|
|
839048d87c | ||
|
|
3b3463f889 | ||
|
|
d3396304ec | ||
|
|
ae09caf7ad | ||
|
|
e1052c209b |
4
.github/workflows/node.js.yml
vendored
4
.github/workflows/node.js.yml
vendored
@@ -5,9 +5,9 @@ name: CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ main ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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": "master"
|
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Find here a collection of samples for the [SAP Cloud Application Programming Mod
|
|||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
If you've [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/master.zip).
|
If you've [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/main.zip).
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/sap-samples/cloud-cap-samples samples
|
git clone https://github.com/sap-samples/cloud-cap-samples samples
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const books = new Vue ({
|
|||||||
data: {
|
data: {
|
||||||
list: [],
|
list: [],
|
||||||
book: undefined,
|
book: undefined,
|
||||||
order: { amount:1, succeeded:'', failed:'' }
|
order: { quantity:1, succeeded:'', failed:'' }
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -26,18 +26,18 @@ const books = new Vue ({
|
|||||||
const book = books.book = books.list [eve.currentTarget.rowIndex-1]
|
const book = books.book = books.list [eve.currentTarget.rowIndex-1]
|
||||||
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`)
|
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`)
|
||||||
Object.assign (book, res.data)
|
Object.assign (book, res.data)
|
||||||
books.order = { amount:1 }
|
books.order = { quantity:1 }
|
||||||
setTimeout (()=> $('form > input').focus(), 111)
|
setTimeout (()=> $('form > input').focus(), 111)
|
||||||
},
|
},
|
||||||
|
|
||||||
async submitOrder () {
|
async submitOrder () {
|
||||||
const {book,order} = books, amount = parseInt (order.amount) || 1 // REVISIT: Okra should be less strict
|
const {book,order} = books, quantity = parseInt (order.quantity) || 1 // REVISIT: Okra should be less strict
|
||||||
try {
|
try {
|
||||||
const res = await POST(`/submitOrder`, { amount, book: book.ID })
|
const res = await POST(`/submitOrder`, { quantity, book: book.ID })
|
||||||
book.stock = res.data.stock
|
book.stock = res.data.stock
|
||||||
books.order = { amount, succeeded: `Successfully ordered ${amount} item(s).` }
|
books.order = { quantity, succeeded: `Successfully ordered ${quantity} item(s).` }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
books.order = { amount, failed: e.response.data.error.message }
|
books.order = { quantity, failed: e.response.data.error.message }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
{{ book.stock }} in stock
|
{{ book.stock }} in stock
|
||||||
</label>
|
</label>
|
||||||
<form @submit.prevent="submitOrder" style="float:right; display:flex; flex-direction:row-reverse">
|
<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="number" v-model="order.quantity" v-bind:class="{ failed: order.failed }" style="width:5em">
|
||||||
<input type="submit" value="Order:" class="muted-button">
|
<input type="submit" value="Order:" class="muted-button">
|
||||||
</form>
|
</form>
|
||||||
<h4> {{ book.title }} </h4>
|
<h4> {{ book.title }} </h4>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
exports.CatalogService = require('./srv/cat-service')
|
const { CatalogService } = require('./srv/cat-service')
|
||||||
|
module.exports = { CatalogService }
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ service CatalogService @(path:'/browse') {
|
|||||||
} excluding { createdBy, modifiedBy };
|
} excluding { createdBy, modifiedBy };
|
||||||
|
|
||||||
@requires: 'authenticated-user'
|
@requires: 'authenticated-user'
|
||||||
action submitOrder ( book: Books:ID, amount: Integer ) returns { stock: Integer };
|
action submitOrder ( book: Books:ID, quantity: Integer ) returns { stock: Integer };
|
||||||
event OrderedBook : { book: Books:ID; amount: Integer; buyer: String };
|
event OrderedBook : { book: Books:ID; quantity: Integer; buyer: String };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,amount} = 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 >= amount) {
|
if (quantity > stock) return req.reject (409,`${quantity} exceeds stock for book #${book}`)
|
||||||
await UPDATE (Books,book) .with (`stock -=`, amount)
|
await UPDATE (Books,book) .with ({ stock: stock -= quantity })
|
||||||
await this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
|
await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id })
|
||||||
return { stock }
|
return { stock }
|
||||||
}
|
|
||||||
else return req.error (409,`${amount} exceeds stock for book #${book}`)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add some discount for overstocked books
|
// Add some discount for overstocked books
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -71,7 +84,7 @@ POST {{server}}/browse/submitOrder
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
{{me}}
|
{{me}}
|
||||||
|
|
||||||
{ "book":201, "amount":5 }
|
{ "book":201, "quantity":5 }
|
||||||
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
### ------------------------------------------------------------------------
|
||||||
|
|||||||
2
bookstore/index.cds
Normal file
2
bookstore/index.cds
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
namespace sap.capire.bookshop; //> important for reflection
|
||||||
|
using from './srv/mashup';
|
||||||
33
bookstore/package.json
Normal file
33
bookstore/package.json
Normal 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
21
bookstore/server.js
Normal 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
|
||||||
@@ -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';
|
||||||
@@ -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,30 +20,29 @@ 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)})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
//
|
//
|
||||||
// Create an order with the OrdersService when CatalogService signals a new order
|
// Create an order with the OrdersService when CatalogService signals a new order
|
||||||
//
|
//
|
||||||
CatalogService.on ('OrderedBook', async (msg) => {
|
CatalogService.on ('OrderedBook', async (msg) => {
|
||||||
const { book, amount, buyer } = msg.data
|
const { book, quantity, buyer } = msg.data
|
||||||
const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price })
|
const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price })
|
||||||
return OrdersService.tx(msg).create ('Orders').entries({
|
return OrdersService.tx(msg).create ('Orders').entries({
|
||||||
OrderNo: 'Order at '+ (new Date).toLocaleString(),
|
OrderNo: 'Order at '+ (new Date).toLocaleString(),
|
||||||
Items: [{ product:{ID:`${book}`}, title, price, amount }],
|
Items: [{ product:{ID:`${book}`}, title, price, quantity }],
|
||||||
buyer, createdBy: buyer
|
buyer, createdBy: buyer
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
//
|
//
|
||||||
// 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
|
|
||||||
})
|
})
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -51,9 +50,9 @@ module.exports = async()=>{ // called by server.js
|
|||||||
//
|
//
|
||||||
OrdersService.on ('OrderChanged', (msg) => {
|
OrdersService.on ('OrderChanged', (msg) => {
|
||||||
console.debug ('> received:', msg.event, msg.data)
|
console.debug ('> received:', msg.event, msg.data)
|
||||||
const { product, deltaAmount } = msg.data
|
const { product, deltaQuantity } = msg.data
|
||||||
return UPDATE (Books) .where ('ID =', product)
|
return UPDATE (Books) .where ('ID =', product)
|
||||||
.and ('stock >=', deltaAmount)
|
.and ('stock >=', deltaQuantity)
|
||||||
.set ('stock -=', deltaAmount)
|
.set ('stock -=', deltaQuantity)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
10
bookstore/srv/swagger-ui.js
Normal file
10
bookstore/srv/swagger-ui.js
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# cds.requires.messaging.kind = file-based-messaging
|
|
||||||
PORT = 4004
|
|
||||||
47
fiori/app/admin-authors/fiori-service.cds
Normal file
47
fiori/app/admin-authors/fiori-service.cds
Normal 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; }
|
||||||
7
fiori/app/admin-authors/webapp/Component.js
Normal file
7
fiori/app/admin-authors/webapp/Component.js
Normal 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 */
|
||||||
11
fiori/app/admin-authors/webapp/i18n/i18n.properties
Normal file
11
fiori/app/admin-authors/webapp/i18n/i18n.properties
Normal 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
|
||||||
145
fiori/app/admin-authors/webapp/manifest.json
Normal file
145
fiori/app/admin-authors/webapp/manifest.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using { AdminService, sap.capire.bookshop } 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,11 +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 and Books.texts through AdminService
|
// 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 {
|
||||||
entity Languages as projection on sap.common.Languages;
|
@readonly entity Languages as projection on sap.common.Languages;
|
||||||
entity Books.texts as projection on bookshop.Books.texts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Workaround for Fiori popup for asking user to enter a new UUID on Create
|
||||||
|
annotate AdminService.Books with { ID @Core.Computed; }
|
||||||
@@ -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" }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -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" : {
|
||||||
142
fiori/app/appconfig/fioriSandboxConfig.json
Normal file
142
fiori/app/appconfig/fioriSandboxConfig.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<head>
|
|
||||||
<meta http-equiv="refresh" content="0;url=bookshop/index.html">
|
|
||||||
</head>
|
|
||||||
@@ -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 : ' '
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, );
|
||||||
|
|||||||
@@ -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": [],
|
||||||
|
|||||||
@@ -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}';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<head>
|
|
||||||
<meta http-equiv="refresh" content="0;url=reviews/index.html">
|
|
||||||
</head>
|
|
||||||
@@ -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';
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +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
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const cds_swagger = require ('cds-swagger-ui-express')
|
|
||||||
cds.once ('bootstrap', app => app.use (cds_swagger()) )
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// 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)))
|
|
||||||
|
|||||||
15
hello/README.md
Normal file
15
hello/README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Hello World Getting Started Sample
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- To run the JavaScript implementation, open a new terminal and run `cds watch`.
|
||||||
|
- To run the TypeScript implementation, open a new terminal and run `cds-ts watch`.
|
||||||
|
|
||||||
|
Then call the service at: http://localhost:4004/say/hello(to='world')
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
Learn more about:
|
||||||
|
|
||||||
|
- [Hello World!](https://cap.cloud.sap/docs/get-started/hello-world)
|
||||||
|
- [Using TypeScript](https://cap.cloud.sap/docs/get-started/using-typescript)
|
||||||
@@ -3,12 +3,12 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npx jest --silent",
|
"test": "npx jest --silent",
|
||||||
"watch": "cds serve world.cds",
|
"start": "cds serve srv/world.cds",
|
||||||
"watch:ts": "cds-ts serve 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"
|
||||||
},
|
},
|
||||||
@@ -25,5 +25,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"env": {
|
||||||
|
"es2020": true,
|
||||||
|
"node": true,
|
||||||
|
"jest": true,
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"SELECT": true,
|
||||||
|
"INSERT": true,
|
||||||
|
"UPDATE": true,
|
||||||
|
"DELETE": true,
|
||||||
|
"CREATE": true,
|
||||||
|
"DROP": true,
|
||||||
|
"CDL": true,
|
||||||
|
"CQL": true,
|
||||||
|
"CXL": true,
|
||||||
|
"cds": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-console": "off",
|
||||||
|
"require-atomic-updates": "off"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
hello/srv/world.ts
Normal file
7
hello/srv/world.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { Request } from "@sap/cds/apis/services"
|
||||||
|
|
||||||
|
module.exports = class say {
|
||||||
|
hello(req: Request) {
|
||||||
|
return `Hello ${req.data.to} from a TypeScript file!`
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ process.env.CDS_TYPESCRIPT = 'true';
|
|||||||
import * as cds from '@sap/cds';
|
import * as cds from '@sap/cds';
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const {GET} = cds.test.in(__dirname,'..').run('serve', 'world.cds');
|
const {GET} = cds.test.in(__dirname,'../srv').run('serve', 'world.cds');
|
||||||
|
|
||||||
describe('Hello world!', () => {
|
describe('Hello world!', () => {
|
||||||
afterAll(() => { delete process.env.CDS_TYPESCRIPT; });
|
afterAll(() => { delete process.env.CDS_TYPESCRIPT; });
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = class say {
|
|
||||||
hello(req: any) {
|
|
||||||
return `Hello ${req.data.to} from a typescript file!`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
using { OrdersService } from '../../srv/orders-service';
|
using { OrdersService } from '../srv/orders-service';
|
||||||
|
|
||||||
|
|
||||||
@odata.draft.enabled
|
@odata.draft.enabled
|
||||||
@@ -68,16 +68,16 @@ 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'},
|
||||||
{Value: title, Label:'Product Title'},
|
{Value: title, Label:'Product Title'},
|
||||||
{Value: price, Label:'Unit Price'},
|
{Value: price, Label:'Unit Price'},
|
||||||
{Value: amount, Label:'Quantity'},
|
{Value: quantity, Label:'Quantity'},
|
||||||
],
|
],
|
||||||
Identification: [ //Is the main field group
|
Identification: [ //Is the main field group
|
||||||
{Value: amount, Label:'Amount'},
|
{Value: quantity, Label:'Quantity'},
|
||||||
{Value: title, Label:'Product'},
|
{Value: title, Label:'Product'},
|
||||||
{Value: price, Label:'Unit Price'},
|
{Value: price, Label:'Unit Price'},
|
||||||
],
|
],
|
||||||
@@ -86,7 +86,7 @@ annotate OrdersService.Orders_Items with @(
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
amount @(
|
quantity @(
|
||||||
Common.FieldControl: #Mandatory
|
Common.FieldControl: #Mandatory
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
/*
|
|
||||||
This model controls what gets served to Fiori frontends...
|
|
||||||
*/
|
|
||||||
|
|
||||||
using from './orders/fiori-service';
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
ID;up__ID;amount;product_ID;title;price
|
ID;up__ID;quantity;product_ID;title;price
|
||||||
58040e66-1dcd-4ffb-ab10-fdce32028b79;7e2f2640-6866-4dcf-8f4d-3027aa831cad;1;201;Wuthering Heights;11.11
|
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
|
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
|
e9641166-e050-4261-bfee-d1e797e6cb7f;64e718c9-ff99-47f1-8ca3-950c850777d4;2;252;Eleonora;28
|
||||||
|
500 Internal Server Error
Gitea Version: 1.24.4 |