Compare commits

..

1 Commits

Author SHA1 Message Date
Christian Georgi
7732763a75 How to run embedded OrderService?
cds deploy excludes its definitions if `OrdersService` is marked
as external.  How would we tell it to include them in the embedded case?
2021-07-13 17:02:43 +02:00
189 changed files with 25740 additions and 9694 deletions

View File

@@ -8,7 +8,7 @@
"mocha": true
},
"parserOptions": {
"ecmaVersion": 2020
"ecmaVersion": 2018
},
"globals": {
"SELECT": true,
@@ -22,7 +22,6 @@
"rules": {
"no-console": "off",
"require-atomic-updates": "off",
"require-await":"warn",
"no-unused-vars": ["warn", { "argsIgnorePattern": "_" }]
"require-await":"warn"
}
}

View File

@@ -1,8 +0,0 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
versioning-strategy: increase-if-necessary
schedule:
interval: daily

View File

@@ -5,9 +5,9 @@ name: CI
on:
push:
branches: [ main ]
branches: [ master ]
pull_request:
branches: [ main ]
branches: [ master ]
jobs:
build:
@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [16.x, 14.x]
node-version: [12.x, 14.x]
steps:
- uses: actions/checkout@v2
@@ -24,6 +24,5 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm i -g npm@8
- run: npm ci
- run: npm test

1
.gitignore vendored
View File

@@ -12,7 +12,6 @@ target/
*.mtar
connection.properties
default-env.json
.cdsrc-private.json
packages/messageBox
reviews/msg-box
reviews/db/test.db

View File

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

View File

@@ -68,35 +68,26 @@
},
{
"file": "fiori/package.json",
"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.",
"selection": {
"start": {
"line": 41,
"character": 1
},
"end": {
"line": 48,
"character": 1
}
},
"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.",
"line": 17,
"title": "Configuration"
},
{
"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",
"line": 43,
"line": 28,
"title": "Run with SQLite"
},
{
"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.",
"line": 46,
"line": 31,
"title": "Run with SAP HANA"
},
{
"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).",
"line": 72,
"line": 68,
"selection": {
"start": {
"line": 67,
@@ -113,5 +104,6 @@
"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."
}
]
],
"ref": "master"
}

View File

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

View File

@@ -13,6 +13,5 @@
"**/cds/lib/req/cls.js",
"**/odata-v4/okra/**"
]
},
"mochaExplorer.parallel": true
}
}

View File

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

View File

@@ -7,18 +7,17 @@ Find here a collection of samples for the [SAP Cloud Application Programming Mod
### Preliminaries
1. Ensure you have the latest LTS version of Node.js installed (see [Getting Started](https://cap.cloud.sap/docs/get-started/))
2. Install [**@sap/cds-dk**](https://cap.cloud.sap/docs/get-started/) globally:
1. Install [**@sap/cds-dk**](https://cap.cloud.sap/docs/get-started/) globally:
```sh
npm i -g @sap/cds-dk
```
3. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/tools#vscode)
2. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/tools#vscode)
### Download
If you've [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/main.zip).
If you've [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/master.zip).
```sh
git clone https://github.com/sap-samples/cloud-cap-samples samples
@@ -84,4 +83,4 @@ In case you've a question, find a bug, or otherwise need support, use our [commu
## License
Copyright (c) 2022 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE) file.
Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE.txt) file.

View File

@@ -1,3 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('_')

View File

@@ -1,35 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
export namespace Association {
export type to <T> = T & ((fn:(a:T)=>any) => T)
export namespace to {
// type many <T> = T[] & (T extends (infer R)[] ? R[] & ((fn:(a:R)=>any) => R[]) : T[]);
export type many <T extends readonly unknown[]> = T & ((fn:(a:T[number])=>any) => T[number]);
}
}
export namespace Composition {
export type of <T> = T & ((fn:(a:T)=>any) => T)
export namespace of {
//type many <T> = T[] & (T extends (infer R)[] ? R[] & ((fn:(a:R)=>any) => R[]) : T[]);
export type many <T extends readonly unknown[]> = T & ((fn:(a:T[number])=>any) => T[number]);
}
}
export class Entity {
static data<T extends Entity> (this:T, input:Object) : T {
return {} as T // mock
}
}
export type EntitySet<T> = T[] & {
data (input:object[]) : T[]
data (input:object) : T
}

View File

@@ -1,3 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('')

View File

@@ -1,57 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as _sap_common from './sap/common';
import * as __ from './_';
export type Language = __.Association.to<_sap_common.Language>;
export type Currency = __.Association.to<_sap_common.Currency>;
export type Country = __.Association.to<_sap_common.Country>;
export type User = string;
// the following represents the CDS aspect 'cuid'
export function cuid<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class cuidAspect extends Base {
ID: string;
};
}
const cuidXtended = cuid(__.Entity)
export type cuid = InstanceType<typeof cuidXtended>
// the following represents the CDS aspect 'managed'
export function managed<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class managedAspect extends Base {
createdAt: Date;
/**
* Canonical user ID
*/
createdBy: User;
modifiedAt: Date;
/**
* Canonical user ID
*/
modifiedBy: User;
};
}
const managedXtended = managed(__.Entity)
export type managed = InstanceType<typeof managedXtended>
// the following represents the CDS aspect 'temporal'
export function temporal<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class temporalAspect extends Base {
validFrom: Date;
validTo: Date;
};
}
const temporalXtended = temporal(__.Entity)
export type temporal = InstanceType<typeof temporalXtended>
// the following represents the CDS aspect 'extensible'
export function extensible<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class extensibleAspect extends Base {
extensions__: string;
};
}
const extensibleXtended = extensible(__.Entity)
export type extensible = InstanceType<typeof extensibleXtended>

View File

@@ -1,9 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('sap.capire.bookshop')
module.exports.Book = cson.Books
module.exports.Books = cson.Books
module.exports.Author = cson.Authors
module.exports.Authors = cson.Authors
module.exports.Genre = cson.Genres
module.exports.Genres = cson.Genres

View File

@@ -1,66 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as __ from './../../../_';
import * as _ from './../../..';
import * as _sap_common from './../../common';
export function Book<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class BookAspect extends Base {
ID: number;
title: string;
descr: string;
author: __.Association.to<Author>;
genre: __.Association.to<Genre>;
stock: number;
price: number;
/**
* Type for an association to Currencies
*
* See https://cap.cloud.sap/docs/cds/common#type-currency
*/
currency: __.Association.to<_.Currency>;
image: string;
};
}
const BookXtended = _.managed(Book(__.Entity))
export type Book = InstanceType<typeof BookXtended>
export class Books extends Array<Book> {
}
export function Author<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class AuthorAspect extends Base {
ID: number;
name: string;
dateOfBirth: Date;
dateOfDeath: Date;
placeOfBirth: string;
placeOfDeath: string;
books: __.Association.to.many<Books>;
};
}
const AuthorXtended = _.managed(Author(__.Entity))
export type Author = InstanceType<typeof AuthorXtended>
export class Authors extends Array<Author> {
}
/**
* Hierarchically organized Code List for Genres
*/
export function Genre<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class GenreAspect extends Base {
ID: number;
parent: __.Association.to<Genre>;
children: __.Composition.of.many<Genres>;
};
}
const GenreXtended = _sap_common.CodeList(Genre(__.Entity))
export type Genre = InstanceType<typeof GenreXtended>
export class Genres extends Array<Genre> {
}

View File

@@ -1,9 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('sap.common')
module.exports.Language = cson.Languages
module.exports.Languages = cson.Languages
module.exports.Country = cson.Countries
module.exports.Countries = cson.Countries
module.exports.Currency = cson.Currencies
module.exports.Currencies = cson.Currencies

View File

@@ -1,68 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as __ from './../../_';
export type Locale = string;
// the following represents the CDS aspect 'CodeList'
export function CodeList<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class CodeListAspect extends Base {
name: string;
descr: string;
};
}
const CodeListXtended = CodeList(__.Entity)
export type CodeList = InstanceType<typeof CodeListXtended>
/**
* Code list for languages
*
* See https://cap.cloud.sap/docs/cds/common#entity-sapcommonlanguages
*/
export function Language<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class LanguageAspect extends Base {
/**
* Type for a language code
*/
code: Locale;
};
}
const LanguageXtended = CodeList(Language(__.Entity))
export type Language = InstanceType<typeof LanguageXtended>
export class Languages extends Array<Language> {
}
/**
* Code list for countries
*
* See https://cap.cloud.sap/docs/cds/common#entity-sapcommoncountries
*/
export function Country<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class CountryAspect extends Base {
code: string;
};
}
const CountryXtended = CodeList(Country(__.Entity))
export type Country = InstanceType<typeof CountryXtended>
export class Countries extends Array<Country> {
}
/**
* Code list for currencies
*
* See https://cap.cloud.sap/docs/cds/common#entity-sapcommoncurrencies
*/
export function Currency<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class CurrencyAspect extends Base {
code: string;
symbol: string;
};
}
const CurrencyXtended = CodeList(Currency(__.Entity))
export type Currency = InstanceType<typeof CurrencyXtended>
export class Currencies extends Array<Currency> {
}

View File

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

View File

@@ -3,15 +3,14 @@ const $ = sel => document.querySelector(sel)
const GET = (url) => axios.get('/browse'+url)
const POST = (cmd,data) => axios.post('/browse'+cmd,data)
const books = Vue.createApp ({
const books = new Vue ({
data() {
return {
el:'#app',
data: {
list: [],
book: undefined,
order: { quantity:1, succeeded:'', failed:'' },
user: undefined
}
order: { amount:1, succeeded:'', failed:'' }
},
methods: {
@@ -27,41 +26,23 @@ const books = Vue.createApp ({
const book = books.book = books.list [eve.currentTarget.rowIndex-1]
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`)
Object.assign (book, res.data)
books.order = { quantity:1 }
books.order = { amount:1 }
setTimeout (()=> $('form > input').focus(), 111)
},
async submitOrder () {
const {book,order} = books, quantity = parseInt (order.quantity) || 1 // REVISIT: Okra should be less strict
const {book,order} = books, amount = parseInt (order.amount) || 1 // REVISIT: Okra should be less strict
try {
const res = await POST(`/submitOrder`, { quantity, book: book.ID })
const res = await POST(`/submitOrder`, { amount, book: book.ID })
book.stock = res.data.stock
books.order = { quantity, succeeded: `Successfully ordered ${quantity} item(s).` }
books.order = { amount, succeeded: `Successfully ordered ${amount} item(s).` }
} catch (e) {
books.order = { quantity, failed: e.response.data.error ? e.response.data.error.message : e.response.data }
books.order = { amount, failed: e.response.data.error.message }
}
},
}
async login() {
try {
const { data:user } = await axios.post('/user/login',{})
if (user.id !== 'anonymous') books.user = user
} catch (err) { books.user = { id: err.message } }
},
async getUserInfo() {
try {
const { data:user } = await axios.get('/user/me')
if (user.id !== 'anonymous') books.user = user
} catch (err) { books.user = { id: err.message } }
},
}
}).mount("#app")
books.getUserInfo()
books.fetch() // initially fill list of books
document.addEventListener('keydown', (event) => {
// hide user info on request
if (event.key === 'u') books.user = undefined
})
// initially fill list of books
books.fetch()

View File

@@ -5,32 +5,19 @@
<title> Capire Books </title>
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<style>
.hovering tr:hover td { color:cyan; background: #123; cursor: pointer; }
.rating-stars { color:teal }
.succeeded { color:teal }
.failed { color:red }
.user {text-align: end; color: grey;}
</style>
</head>
<body class="small-container", style="margin-top: 70px;">
<div id='app'>
<form class="user" @submit.prevent="login">
<div v-if="user">
<div v-if="user.tenant">Tenant: {{ user.tenant }}</div>
<div> User: {{ user.id }}</div>
<div>Locale: {{ user.locale }}</div>
</div>
<div v-else>
<input type="submit" value="Login" class="muted-button">
<!-- <a href="/user/login()">Login</a> -->
</div>
</form>
<h1> Capire Books </h1>
<h1> {{ document.title }} </h1>
<input type="text" placeholder="Search..." @input="search">
@@ -47,7 +34,7 @@
<td>{{ book.author }}</td>
<td>{{ book.genre.name }}</td>
<td class="rating-stars">
{{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }} ({{ book.numberOfReviews }})
{{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }}
</td>
<td>{{ book.currency && book.currency.symbol }} {{ book.price }}</td>
</tr>
@@ -61,7 +48,7 @@
&nbsp;&nbsp; {{ book.stock }} in stock
</label>
<form @submit.prevent="submitOrder" style="float:right; display:flex; flex-direction:row-reverse">
<input type="number" v-model="order.quantity" v-bind:class="{ failed: order.failed }" style="width:5em">
<input type="number" v-model="order.amount" v-bind:class="{ failed: order.failed }" style="width:5em">
<input type="submit" value="Order:" class="muted-button">
</form>
<h4> {{ book.title }} </h4>

View File

@@ -1,24 +0,0 @@
/**
* In order to keep basic bookshop sample as simple as possible, we don't add
* reuse dependencies. This db/init.js ensures we still have a minimum set of
* currencies, if not obtained through @capire/common.
*/
module.exports = async (db)=>{
const has_common = db.model.definitions['sap.common.Currencies'].elements.numcode
if (has_common) return
const already_filled = await db.exists('sap.common.Currencies',{code:'EUR'})
if (already_filled) return
await INSERT.into ('sap.common.Currencies') .columns (
'code','symbol','name'
) .rows (
[ 'EUR','€','Euro' ],
[ 'USD','$','US Dollar' ],
[ 'GBP','£','British Pound' ],
[ 'ILS','₪','Shekel' ],
[ 'JPY','¥','Yen' ],
)
}

View File

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

View File

@@ -1,5 +0,0 @@
{
"compilerOptions": {
"checkJs": true
}
}

View File

@@ -2,17 +2,11 @@
"name": "@capire/bookshop",
"version": "1.0.0",
"description": "A simple self-contained bookshop service.",
"files": [
"app",
"srv",
"db",
"index.cds",
"index.js"
],
"dependencies": {
"@sap/cds": ">=5.9",
"@capire/common": "*",
"@sap/cds": "^5.0.4",
"express": "^4.17.1",
"passport": ">=0.4.1"
"passport": "0.4.1"
},
"scripts": {
"genres": "cds serve test/genres.cds",

View File

@@ -11,6 +11,6 @@ service CatalogService @(path:'/browse') {
} excluding { createdBy, modifiedBy };
@requires: 'authenticated-user'
action submitOrder ( book: Books:ID, quantity: Integer ) returns { stock: Integer };
event OrderedBook : { book: Books:ID; quantity: Integer; buyer: String };
action submitOrder ( book: Books:ID, amount: Integer ) returns { stock: Integer };
event OrderedBook : { book: Books:ID; amount: Integer; buyer: String };
}

View File

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

View File

@@ -1,15 +0,0 @@
/**
* Exposes user information
*/
service UserService {
/**
* The current user
*/
@odata.singleton entity me {
id : String; // user id
locale : String;
tenant : String;
}
action login() returns me;
}

View File

@@ -1,9 +0,0 @@
const cds = require('@sap/cds')
module.exports = class UserService extends cds.Service { init(){
this.on('READ', 'me', ({ tenant, user, locale }) => ({ id: user.id, locale, tenant }))
this.on('login', (req) => {
if (req.user._is_anonymous)
req._.res.set('WWW-Authenticate','Basic realm="Users"').sendStatus(401)
else return this.read('me')
})
}}

View File

@@ -16,9 +16,9 @@ GET {{server}}/browse/$metadata
### ------------------------------------------------------------------------
# Browse Books as any user
GET {{server}}/browse/ListOfBooks?
GET {{server}}/browse/Books?
# &$select=title,stock
&$expand=genre
# &$expand=currency
# &sap-language=de
{{me}}
@@ -32,19 +32,6 @@ GET {{server}}/admin/Authors?
# &sap-language=de
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
POST {{server}}/admin/Books
@@ -84,7 +71,7 @@ POST {{server}}/browse/submitOrder
Content-Type: application/json
{{me}}
{ "book":201, "quantity":5 }
{ "book":201, "amount":5 }
### ------------------------------------------------------------------------

View File

@@ -1,7 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('AdminService')
module.exports.Book = cson.Books
module.exports.Books = cson.Books
module.exports.Author = cson.Authors
module.exports.Authors = cson.Authors

View File

@@ -1,54 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as _sap_capire_bookshop from './../sap/capire/bookshop';
import * as __ from './../_';
import * as _ from './..';
import * as _ReviewsService from './../ReviewsService';
export function Book<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class BookAspect extends Base {
ID: number;
title: string;
descr: string;
author: __.Association.to<_sap_capire_bookshop.Author>;
genre: __.Association.to<_sap_capire_bookshop.Genre>;
stock: number;
price: number;
/**
* Type for an association to Currencies
*
* See https://cap.cloud.sap/docs/cds/common#type-currency
*/
currency: __.Association.to<_.Currency>;
image: string;
reviews: __.Composition.of.many<_ReviewsService.Reviews>;
rating: number;
numberOfReviews: number;
};
}
const BookXtended = _.managed(Book(__.Entity))
export type Book = InstanceType<typeof BookXtended>
export class Books extends Array<Book> {
}
export function Author<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class AuthorAspect extends Base {
ID: number;
name: string;
dateOfBirth: Date;
dateOfDeath: Date;
placeOfBirth: string;
placeOfDeath: string;
books: __.Association.to.many<_sap_capire_bookshop.Books>;
};
}
const AuthorXtended = _.managed(Author(__.Entity))
export type Author = InstanceType<typeof AuthorXtended>
export class Authors extends Array<Author> {
}

View File

@@ -1,7 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('CatalogService')
module.exports.ListOfBook = cson.ListOfBooks
module.exports.ListOfBooks = cson.ListOfBooks
module.exports.Book = cson.Books
module.exports.Books = cson.Books

View File

@@ -1,72 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as _sap_capire_bookshop from './../sap/capire/bookshop';
import * as __ from './../_';
import * as _ from './..';
import * as _ReviewsService from './../ReviewsService';
/**
* For displaying lists of Books
*/
export function ListOfBook<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class ListOfBookAspect extends Base {
ID: number;
title: string;
descr: string;
author: __.Association.to<_sap_capire_bookshop.Author>;
genre: __.Association.to<_sap_capire_bookshop.Genre>;
stock: number;
price: number;
/**
* Type for an association to Currencies
*
* See https://cap.cloud.sap/docs/cds/common#type-currency
*/
currency: __.Association.to<_.Currency>;
image: string;
reviews: __.Composition.of.many<_ReviewsService.Reviews>;
rating: number;
numberOfReviews: number;
};
}
const ListOfBookXtended = _.managed(ListOfBook(__.Entity))
export type ListOfBook = InstanceType<typeof ListOfBookXtended>
export class ListOfBooks extends Array<ListOfBook> {
}
/**
* For display in details pages
*/
export function Book<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class BookAspect extends Base {
ID: number;
title: string;
descr: string;
author: __.Association.to<_sap_capire_bookshop.Author>;
genre: __.Association.to<_sap_capire_bookshop.Genre>;
stock: number;
price: number;
/**
* Type for an association to Currencies
*
* See https://cap.cloud.sap/docs/cds/common#type-currency
*/
currency: __.Association.to<_.Currency>;
image: string;
reviews: __.Composition.of.many<_ReviewsService.Reviews>;
rating: number;
numberOfReviews: number;
};
}
const BookXtended = _.managed(Book(__.Entity))
export type Book = InstanceType<typeof BookXtended>
export class Books extends Array<Book> {
}
// action
export declare const submitOrder: (book: Books, quantity: number) => {};

View File

@@ -1,7 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('DataService')
module.exports.Entity = cson.Entities
module.exports.Entities = cson.Entities
module.exports.Data = cson.Data
module.exports.Data_ = cson.Data

View File

@@ -1,40 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as __ from './../_';
/**
* Metadata like name and columns/elements
*/
export function Entity<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class EntityAspect extends Base {
name: string;
columns: {
name: string;
type: string;
isKey: boolean;
};
};
}
const EntityXtended = Entity(__.Entity)
export type Entity = InstanceType<typeof EntityXtended>
export class Entities extends Array<Entity> {
}
/**
* The actual data, organized by column name
*/
export function Data<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class DataAspect extends Base {
record: {};
};
}
const DataXtended = Data(__.Entity)
export type Data = InstanceType<typeof DataXtended>
export class Data_ extends Array<Data> {
}

View File

@@ -1,5 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('OrdersService')
module.exports.Order = cson.Orders
module.exports.Orders = cson.Orders

View File

@@ -1,39 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as _sap_capire_orders from './../sap/capire/orders';
import * as __ from './../_';
import * as _sap_capire_bookshop from './../sap/capire/bookshop';
import * as _ from './..';
export function Order<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class OrderAspect extends Base {
OrderNo: string;
Items: {
ID: string;
product: __.Association.to<_sap_capire_orders.Product>;
quantity: number;
title: string;
price: number;
book: __.Association.to<_sap_capire_bookshop.Book>;
};
/**
* Canonical user ID
*/
buyer: _.User;
/**
* Type for an association to Currencies
*
* See https://cap.cloud.sap/docs/cds/common#type-currency
*/
currency: __.Association.to<_.Currency>;
};
}
const OrderXtended = _.cuid(_.managed(Order(__.Entity)))
export type Order = InstanceType<typeof OrderXtended>
export class Orders extends Array<Order> {
}

View File

@@ -1,5 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('ReviewsService')
module.exports.Review = cson.Reviews
module.exports.Reviews = cson.Reviews

View File

@@ -1,35 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as _sap_capire_reviews from './../sap/capire/reviews';
import * as _ from './..';
import * as __ from './../_';
export function Review<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class ReviewAspect extends Base {
ID: string;
subject: _sap_capire_reviews.ReviewedSubject;
/**
* Canonical user ID
*/
reviewer: _.User;
rating: _sap_capire_reviews.Rating;
title: string;
text: string;
date: Date;
likes: __.Composition.of.many<_sap_capire_reviews.Likes>;
liked: number;
};
}
const ReviewXtended = Review(__.Entity)
export type Review = InstanceType<typeof ReviewXtended>
export class Reviews extends Array<Review> {
}
// action
export declare const like: (review: Reviews) => {};
// action
export declare const unlike: (review: Reviews) => {};

View File

@@ -1,3 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('_')

View File

@@ -1,35 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
export namespace Association {
export type to <T> = T & ((fn:(a:T)=>any) => T)
export namespace to {
// type many <T> = T[] & (T extends (infer R)[] ? R[] & ((fn:(a:R)=>any) => R[]) : T[]);
export type many <T extends readonly unknown[]> = T & ((fn:(a:T[number])=>any) => T[number]);
}
}
export namespace Composition {
export type of <T> = T & ((fn:(a:T)=>any) => T)
export namespace of {
//type many <T> = T[] & (T extends (infer R)[] ? R[] & ((fn:(a:R)=>any) => R[]) : T[]);
export type many <T extends readonly unknown[]> = T & ((fn:(a:T[number])=>any) => T[number]);
}
}
export class Entity {
static data<T extends Entity> (this:T, input:Object) : T {
return {} as T // mock
}
}
export type EntitySet<T> = T[] & {
data (input:object[]) : T[]
data (input:object) : T
}

View File

@@ -1,3 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('')

View File

@@ -1,57 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as _sap_common from './sap/common';
import * as __ from './_';
export type Language = __.Association.to<_sap_common.Language>;
export type Currency = __.Association.to<_sap_common.Currency>;
export type Country = __.Association.to<_sap_common.Country>;
export type User = string;
// the following represents the CDS aspect 'cuid'
export function cuid<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class cuidAspect extends Base {
ID: string;
};
}
const cuidXtended = cuid(__.Entity)
export type cuid = InstanceType<typeof cuidXtended>
// the following represents the CDS aspect 'managed'
export function managed<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class managedAspect extends Base {
createdAt: Date;
/**
* Canonical user ID
*/
createdBy: User;
modifiedAt: Date;
/**
* Canonical user ID
*/
modifiedBy: User;
};
}
const managedXtended = managed(__.Entity)
export type managed = InstanceType<typeof managedXtended>
// the following represents the CDS aspect 'temporal'
export function temporal<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class temporalAspect extends Base {
validFrom: Date;
validTo: Date;
};
}
const temporalXtended = temporal(__.Entity)
export type temporal = InstanceType<typeof temporalXtended>
// the following represents the CDS aspect 'extensible'
export function extensible<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class extensibleAspect extends Base {
extensions__: string;
};
}
const extensibleXtended = extensible(__.Entity)
export type extensible = InstanceType<typeof extensibleXtended>

View File

@@ -1,9 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('sap.capire.bookshop')
module.exports.Book = cson.Books
module.exports.Books = cson.Books
module.exports.Author = cson.Authors
module.exports.Authors = cson.Authors
module.exports.Genre = cson.Genres
module.exports.Genres = cson.Genres

View File

@@ -1,70 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as __ from './../../../_';
import * as _ from './../../..';
import * as _ReviewsService from './../../../ReviewsService';
import * as _sap_common from './../../common';
export function Book<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class BookAspect extends Base {
ID: number;
title: string;
descr: string;
author: __.Association.to<Author>;
genre: __.Association.to<Genre>;
stock: number;
price: number;
/**
* Type for an association to Currencies
*
* See https://cap.cloud.sap/docs/cds/common#type-currency
*/
currency: __.Association.to<_.Currency>;
image: string;
reviews: __.Composition.of.many<_ReviewsService.Reviews>;
rating: number;
numberOfReviews: number;
};
}
const BookXtended = _.managed(Book(__.Entity))
export type Book = InstanceType<typeof BookXtended>
export class Books extends Array<Book> {
}
export function Author<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class AuthorAspect extends Base {
ID: number;
name: string;
dateOfBirth: Date;
dateOfDeath: Date;
placeOfBirth: string;
placeOfDeath: string;
books: __.Association.to.many<Books>;
};
}
const AuthorXtended = _.managed(Author(__.Entity))
export type Author = InstanceType<typeof AuthorXtended>
export class Authors extends Array<Author> {
}
/**
* Hierarchically organized Code List for Genres
*/
export function Genre<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class GenreAspect extends Base {
ID: number;
parent: __.Association.to<Genre>;
children: __.Composition.of.many<Genres>;
};
}
const GenreXtended = _sap_common.CodeList(Genre(__.Entity))
export type Genre = InstanceType<typeof GenreXtended>
export class Genres extends Array<Genre> {
}

View File

@@ -1,7 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('sap.capire.orders')
module.exports.Order = cson.Orders
module.exports.Orders = cson.Orders
module.exports.Product = cson.Products
module.exports.Products = cson.Products

View File

@@ -1,52 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as __ from './../../../_';
import * as _sap_capire_bookshop from './../bookshop';
import * as _ from './../../..';
export function Order<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class OrderAspect extends Base {
OrderNo: string;
Items: {
ID: string;
product: __.Association.to<Product>;
quantity: number;
title: string;
price: number;
book: __.Association.to<_sap_capire_bookshop.Book>;
};
/**
* Canonical user ID
*/
buyer: _.User;
/**
* Type for an association to Currencies
*
* See https://cap.cloud.sap/docs/cds/common#type-currency
*/
currency: __.Association.to<_.Currency>;
};
}
const OrderXtended = _.cuid(_.managed(Order(__.Entity)))
export type Order = InstanceType<typeof OrderXtended>
export class Orders extends Array<Order> {
}
/**
* This is a stand-in for arbitrary ordered Products
*/
export function Product<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class ProductAspect extends Base {
ID: string;
};
}
const ProductXtended = Product(__.Entity)
export type Product = InstanceType<typeof ProductXtended>
export class Products extends Array<Product> {
}

View File

@@ -1,7 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('sap.capire.reviews')
module.exports.Review = cson.Reviews
module.exports.Reviews = cson.Reviews
module.exports.Like = cson.Likes
module.exports.Likes = cson.Likes

View File

@@ -1,51 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as _ from './../../..';
import * as __ from './../../../_';
export type ReviewedSubject = string;
export enum Rating {
Best = 5,
Good = 4,
Avg = 3,
Poor = 2,
Worst = 1,
}
export function Review<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class ReviewAspect extends Base {
ID: string;
subject: ReviewedSubject;
/**
* Canonical user ID
*/
reviewer: _.User;
rating: Rating;
title: string;
text: string;
date: Date;
likes: __.Composition.of.many<Likes>;
liked: number;
};
}
const ReviewXtended = Review(__.Entity)
export type Review = InstanceType<typeof ReviewXtended>
export class Reviews extends Array<Review> {
}
export function Like<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class LikeAspect extends Base {
review: __.Association.to<Review>;
/**
* Canonical user ID
*/
user: _.User;
};
}
const LikeXtended = Like(__.Entity)
export type Like = InstanceType<typeof LikeXtended>
export class Likes extends Array<Like> {
}

View File

@@ -1,9 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('sap.common.countries')
module.exports.Region = cson.Regions
module.exports.Regions = cson.Regions
module.exports.City = cson.Cities
module.exports.Cities = cson.Cities
module.exports.District = cson.Districts
module.exports.Districts = cson.Districts

View File

@@ -1,47 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as __ from './../../../_';
import * as _sap_common from './..';
export function Region<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class RegionAspect extends Base {
code: string;
children: __.Composition.of.many<Regions>;
cities: __.Composition.of.many<Cities>;
_parent: string;
};
}
const RegionXtended = _sap_common.CodeList(Region(__.Entity))
export type Region = InstanceType<typeof RegionXtended>
export class Regions extends Array<Region> {
}
export function City<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class CityAspect extends Base {
code: string;
region: __.Association.to<Region>;
districts: __.Composition.of.many<Districts>;
};
}
const CityXtended = _sap_common.CodeList(City(__.Entity))
export type City = InstanceType<typeof CityXtended>
export class Cities extends Array<City> {
}
export function District<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class DistrictAspect extends Base {
code: string;
city: __.Association.to<City>;
};
}
const DistrictXtended = _sap_common.CodeList(District(__.Entity))
export type District = InstanceType<typeof DistrictXtended>
export class Districts extends Array<District> {
}

View File

@@ -1,9 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('sap.common')
module.exports.Language = cson.Languages
module.exports.Languages = cson.Languages
module.exports.Country = cson.Countries
module.exports.Countries = cson.Countries
module.exports.Currency = cson.Currencies
module.exports.Currencies = cson.Currencies

View File

@@ -1,73 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as __ from './../../_';
import * as _sap_common_countries from './countries';
export type Locale = string;
// the following represents the CDS aspect 'CodeList'
export function CodeList<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class CodeListAspect extends Base {
name: string;
descr: string;
};
}
const CodeListXtended = CodeList(__.Entity)
export type CodeList = InstanceType<typeof CodeListXtended>
/**
* Code list for languages
*
* See https://cap.cloud.sap/docs/cds/common#entity-sapcommonlanguages
*/
export function Language<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class LanguageAspect extends Base {
/**
* Type for a language code
*/
code: Locale;
};
}
const LanguageXtended = CodeList(Language(__.Entity))
export type Language = InstanceType<typeof LanguageXtended>
export class Languages extends Array<Language> {
}
/**
* Code list for countries
*
* See https://cap.cloud.sap/docs/cds/common#entity-sapcommoncountries
*/
export function Country<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class CountryAspect extends Base {
code: string;
regions: __.Composition.of.many<_sap_common_countries.Regions>;
};
}
const CountryXtended = CodeList(Country(__.Entity))
export type Country = InstanceType<typeof CountryXtended>
export class Countries extends Array<Country> {
}
/**
* Code list for currencies
*
* See https://cap.cloud.sap/docs/cds/common#entity-sapcommoncurrencies
*/
export function Currency<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class CurrencyAspect extends Base {
code: string;
symbol: string;
numcode: number;
exponent: number;
minor: string;
};
}
const CurrencyXtended = CodeList(Currency(__.Entity))
export type Currency = InstanceType<typeof CurrencyXtended>
export class Currencies extends Array<Currency> {
}

View File

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

View File

@@ -1,5 +0,0 @@
{
"compilerOptions": {
"checkJs": true
}
}

View File

@@ -1,34 +0,0 @@
{
"name": "@capire/bookstore",
"version": "1.0.0",
"dependencies": {
"@capire/bookshop": "*",
"@capire/reviews": "*",
"@capire/orders": "*",
"@capire/common": "*",
"@capire/data-viewer": "*",
"@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 }
}
}

View File

@@ -1,22 +0,0 @@
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')
app.serve ('/data') .from('@capire/data-viewer','app/viewer')
})
// 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,38 +0,0 @@
////////////////////////////////////////////////////////////////////////////
//
// Enhancing bookshop with Reviews and Orders provided through
// respective reuse packages and services
//
using { sap.capire.bookshop.Books } from '@capire/bookshop';
//
// Extend Books with access to Reviews and average ratings
//
using { ReviewsService.Reviews } from '@capire/reviews';
extend Books with {
reviews : Composition of many Reviews on reviews.subject = $self.ID;
rating : Decimal;
numberOfReviews : Integer;
}
//
// Extend Orders with Books as Products
//
using { sap.capire.orders.Orders } from '@capire/orders';
extend Orders with {
extend Items with {
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';
// Add data browser
using from '@capire/data-viewer';
// Incorporate pre-build extensions from...
using from '@capire/common';

View File

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

@@ -1,3 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('_')

View File

@@ -1,35 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
export namespace Association {
export type to <T> = T & ((fn:(a:T)=>any) => T)
export namespace to {
// type many <T> = T[] & (T extends (infer R)[] ? R[] & ((fn:(a:R)=>any) => R[]) : T[]);
export type many <T extends readonly unknown[]> = T & ((fn:(a:T[number])=>any) => T[number]);
}
}
export namespace Composition {
export type of <T> = T & ((fn:(a:T)=>any) => T)
export namespace of {
//type many <T> = T[] & (T extends (infer R)[] ? R[] & ((fn:(a:R)=>any) => R[]) : T[]);
export type many <T extends readonly unknown[]> = T & ((fn:(a:T[number])=>any) => T[number]);
}
}
export class Entity {
static data<T extends Entity> (this:T, input:Object) : T {
return {} as T // mock
}
}
export type EntitySet<T> = T[] & {
data (input:object[]) : T[]
data (input:object) : T
}

View File

@@ -1,3 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('')

View File

@@ -1,57 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as _sap_common from './sap/common';
import * as __ from './_';
export type Language = __.Association.to<_sap_common.Language>;
export type Currency = __.Association.to<_sap_common.Currency>;
export type Country = __.Association.to<_sap_common.Country>;
export type User = string;
// the following represents the CDS aspect 'cuid'
export function cuid<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class cuidAspect extends Base {
ID: string;
};
}
const cuidXtended = cuid(__.Entity)
export type cuid = InstanceType<typeof cuidXtended>
// the following represents the CDS aspect 'managed'
export function managed<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class managedAspect extends Base {
createdAt: Date;
/**
* Canonical user ID
*/
createdBy: User;
modifiedAt: Date;
/**
* Canonical user ID
*/
modifiedBy: User;
};
}
const managedXtended = managed(__.Entity)
export type managed = InstanceType<typeof managedXtended>
// the following represents the CDS aspect 'temporal'
export function temporal<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class temporalAspect extends Base {
validFrom: Date;
validTo: Date;
};
}
const temporalXtended = temporal(__.Entity)
export type temporal = InstanceType<typeof temporalXtended>
// the following represents the CDS aspect 'extensible'
export function extensible<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class extensibleAspect extends Base {
extensions__: string;
};
}
const extensibleXtended = extensible(__.Entity)
export type extensible = InstanceType<typeof extensibleXtended>

View File

@@ -1,9 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('sap.common.countries')
module.exports.Region = cson.Regions
module.exports.Regions = cson.Regions
module.exports.City = cson.Cities
module.exports.Cities = cson.Cities
module.exports.District = cson.Districts
module.exports.Districts = cson.Districts

View File

@@ -1,47 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as __ from './../../../_';
import * as _sap_common from './..';
export function Region<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class RegionAspect extends Base {
code: string;
children: __.Composition.of.many<Regions>;
cities: __.Composition.of.many<Cities>;
_parent: string;
};
}
const RegionXtended = _sap_common.CodeList(Region(__.Entity))
export type Region = InstanceType<typeof RegionXtended>
export class Regions extends Array<Region> {
}
export function City<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class CityAspect extends Base {
code: string;
region: __.Association.to<Region>;
districts: __.Composition.of.many<Districts>;
};
}
const CityXtended = _sap_common.CodeList(City(__.Entity))
export type City = InstanceType<typeof CityXtended>
export class Cities extends Array<City> {
}
export function District<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class DistrictAspect extends Base {
code: string;
city: __.Association.to<City>;
};
}
const DistrictXtended = _sap_common.CodeList(District(__.Entity))
export type District = InstanceType<typeof DistrictXtended>
export class Districts extends Array<District> {
}

View File

@@ -1,9 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('sap.common')
module.exports.Language = cson.Languages
module.exports.Languages = cson.Languages
module.exports.Country = cson.Countries
module.exports.Countries = cson.Countries
module.exports.Currency = cson.Currencies
module.exports.Currencies = cson.Currencies

View File

@@ -1,73 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as __ from './../../_';
import * as _sap_common_countries from './countries';
export type Locale = string;
// the following represents the CDS aspect 'CodeList'
export function CodeList<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class CodeListAspect extends Base {
name: string;
descr: string;
};
}
const CodeListXtended = CodeList(__.Entity)
export type CodeList = InstanceType<typeof CodeListXtended>
/**
* Code list for languages
*
* See https://cap.cloud.sap/docs/cds/common#entity-sapcommonlanguages
*/
export function Language<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class LanguageAspect extends Base {
/**
* Type for a language code
*/
code: Locale;
};
}
const LanguageXtended = CodeList(Language(__.Entity))
export type Language = InstanceType<typeof LanguageXtended>
export class Languages extends Array<Language> {
}
/**
* Code list for countries
*
* See https://cap.cloud.sap/docs/cds/common#entity-sapcommoncountries
*/
export function Country<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class CountryAspect extends Base {
code: string;
regions: __.Composition.of.many<_sap_common_countries.Regions>;
};
}
const CountryXtended = CodeList(Country(__.Entity))
export type Country = InstanceType<typeof CountryXtended>
export class Countries extends Array<Country> {
}
/**
* Code list for currencies
*
* See https://cap.cloud.sap/docs/cds/common#entity-sapcommoncurrencies
*/
export function Currency<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class CurrencyAspect extends Base {
code: string;
symbol: string;
numcode: number;
exponent: number;
minor: string;
};
}
const CurrencyXtended = CodeList(Currency(__.Entity))
export type Currency = InstanceType<typeof CurrencyXtended>
export class Currencies extends Array<Currency> {
}

View File

@@ -20,7 +20,7 @@ extend sap.common.Currencies with {
* annotate sap.common.Countries with @cds.persistence.skip:false;
*/
context sap.common.countries {
context sap.common_countries {
extend sap.common.Countries {
regions : Composition of many Regions on regions._parent = $self.code;

View File

@@ -1,5 +0,0 @@
{
"compilerOptions": {
"checkJs": true
}
}

View File

@@ -1,7 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('DataService')
module.exports.Entity = cson.Entities
module.exports.Entities = cson.Entities
module.exports.Data = cson.Data
module.exports.Data_ = cson.Data

View File

@@ -1,40 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
import * as __ from './../_';
/**
* Metadata like name and columns/elements
*/
export function Entity<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class EntityAspect extends Base {
name: string;
columns: {
name: string;
type: string;
isKey: boolean;
};
};
}
const EntityXtended = Entity(__.Entity)
export type Entity = InstanceType<typeof EntityXtended>
export class Entities extends Array<Entity> {
}
/**
* The actual data, organized by column name
*/
export function Data<TBase extends new (...args: any[]) => {}>(Base: TBase) {
return class DataAspect extends Base {
record: {};
};
}
const DataXtended = Data(__.Entity)
export type Data = InstanceType<typeof DataXtended>
export class Data_ extends Array<Data> {
}

View File

@@ -1,3 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
const cds = require('@sap/cds')
const cson = cds.entities('_')

View File

@@ -1,35 +0,0 @@
// This is an automatically generated file. Please do not change its contents manually!
export namespace Association {
export type to <T> = T & ((fn:(a:T)=>any) => T)
export namespace to {
// type many <T> = T[] & (T extends (infer R)[] ? R[] & ((fn:(a:R)=>any) => R[]) : T[]);
export type many <T extends readonly unknown[]> = T & ((fn:(a:T[number])=>any) => T[number]);
}
}
export namespace Composition {
export type of <T> = T & ((fn:(a:T)=>any) => T)
export namespace of {
//type many <T> = T[] & (T extends (infer R)[] ? R[] & ((fn:(a:R)=>any) => R[]) : T[]);
export type many <T extends readonly unknown[]> = T & ((fn:(a:T[number])=>any) => T[number]);
}
}
export class Entity {
static data<T extends Entity> (this:T, input:Object) : T {
return {} as T // mock
}
}
export type EntitySet<T> = T[] & {
data (input:object[]) : T[]
data (input:object) : T
}

View File

@@ -1,119 +0,0 @@
/* global Vue axios */ //> from vue.html
const GET = (url) => axios.get('/-data'+url)
const storageGet = (key, def) => localStorage.getItem('data-viewer:'+key) || def
const storageSet = (key, val) => localStorage.setItem('data-viewer:'+key, val)
const columnKeysFirst = (c1, c2) => {
if (c1.isKey && !c2.isKey) return -1
if (!c1.isKey && c2.isKey) return 1
if (c1.isKey && c2.isKey) return c1.name.localeCompare(c2.name)
return 0 // retain natural order of normal columns
}
const vue = Vue.createApp ({
data() { return {
error: undefined,
dataSource: storageGet('data-source', 'db'),
skip: storageGet('skip', 0),
top: storageGet('top', 20),
entity: storageGet('entity') ? JSON.parse(storageGet('entity')) : undefined,
entities: [],
columns: [],
data: [],
rowDetails: {},
rowKey: storageGet('rowKey')
}},
watch: {
dataSource: (v) => { storageSet('data-source', v); vue.fetchEntities() },
skip: (v) => { storageSet('skip', v); if (vue.entity) vue.fetchData() },
top: (v) => { storageSet('top', v); if (vue.entity) vue.fetchData() },
},
methods: {
async fetchEntities () {
let url = `/Entities`
if (vue.dataSource === 'db') url += `?dataSource=db`
const {data} = await GET(url)
vue.entities = data.value
vue.entities.forEach(entity => entity.columns.sort(columnKeysFirst))
const entity = vue.entity && vue.entities.find(e => e.name === vue.entity.name)
if (entity) { // restore selection from previous fetch
vue.columns = entity.columns
await vue.fetchData(entity)
} else {
vue.entity = undefined
vue.columns = []
vue.data = []
vue.rowDetails = {}
}
},
async inspectEntity (eve) {
const entity = vue.entity = vue.entities [eve.currentTarget.rowIndex-1]
storageSet('entity', JSON.stringify(entity))
vue.columns = vue.entities.find(e => e.name === entity.name).columns
return await this.fetchData()
},
async fetchData () {
let url = `/Data?entity=${vue.entity.name}&$skip=${vue.skip}&$top=${vue.top}`
if (vue.dataSource === 'db') url += `&dataSource=db`
try {
const {data} = await GET(url)
// sort data along column order
const columnIndexes = {}
vue.columns.forEach((col, i) => columnIndexes[col.name] = i)
vue.data = data.value.map(d => d.record
.sort((r1, r2) => columnIndexes[r1.column] - columnIndexes[r2.column])
.map(r => r.data)
)
const row = vue.data.find(data => vue._makeRowKey(data) === vue.rowKey)
if (row) vue._setRowDetails(row)
else vue.rowDetails = {}
vue.error = undefined
} catch (err) {
vue.data = []
vue.rowDetails = {}
if (err.response?.data?.error) {
vue.error = err.response.data.error
} else {
vue.error = { code:err.code, message:err.message }
}
}
},
inspectRow (eve) {
vue.rowDetails = {}
const selectedRow = eve.currentTarget.rowIndex-1
vue.rowKey = vue._makeRowKey(vue.data[selectedRow])
storageSet('rowKey', vue.rowKey)
vue._setRowDetails(vue.data[selectedRow])
},
_setRowDetails(row) {
vue.rowDetails = {}
row.forEach((line, colIndex) => {
vue.rowDetails[vue.columns[colIndex].name] = line
})
},
_makeRowKey(row) {
// to identify a row, build a key string out of all key columns' values
return row
.filter((_, colIndex) => vue.columns[colIndex] && vue.columns[colIndex].isKey)
.reduce(((prev, next) => prev += next), '')
},
isActiveRow(row) {
return vue._makeRowKey(row) === vue.rowKey
}
}
})
.mount('#app')
vue.fetchEntities()

View File

@@ -1,95 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Data Browser</title>
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
<script src="app.js" defer></script>
<style>
th { position: sticky; top:0; z-index: 2; background-color: white; }
.noscroll { overflow: hidden; }
.hovering tr:hover td { background: #ebeefc; cursor: pointer; }
.highlight { background: #ebeefc !important; }
.rating-stars { color:teal }
.succeeded { color:teal }
.failed { color:red }
.condensed { max-width: 100px; text-overflow: ellipsis; white-space: nowrap; }
.key { font-weight: bold }
.not-key { font-weight: lighter;}
.with-sidebar { display: flex; flex-wrap: wrap; gap: 1rem; }
.sidebar { flex-basis: 20rem; flex-grow: 1; }
.sidebar-main { height: 100vh; overflow-y: scroll; }
.not-sidebar { flex-basis: 0; flex-grow: 999; min-inline-size: 50%; align-items: stretch;}
.not-sidebar-main { max-height: 40vh; overflow-y: scroll; }
.not-sidebar-sub { max-height: 40vh; overflow-y: scroll; }
.horizontal label { display: inline; }
.horizontal input { width: initial; display: inline; }
.error { color: red; }
</style>
</head>
<body class="noscroll">
<div id='app' class="full-container">
<h1>Data Browser &ndash; {{ entity ? entity.name : '' }}</h1>
<div class="with-sidebar">
<div class="sidebar">
<div class="horizontal" style="padding: 0.75rem 0;">
<label>Datasource:</label>
<input type="radio" id="dataSource-db" value="db" v-model="dataSource">
<label for="dataSource-db">Database</label>
<input type="radio" id="dataSource-srv" value="service" v-model="dataSource">
<label for="dataSource-srv">Service</label>
</div>
<div class="sidebar-main">
<table id='entities' class="hovering">
<thead>
<th>Entity Name</th>
</thead>
<tr v-for="e in entities" :key="e.name" @click="inspectEntity" :class="{'highlight': (entity && e.name === entity.name)}">
<td>{{ e.name }}</td>
</tr>
</table>
</div>
</div>
<div class="not-sidebar">
<div class="horizontal">
<label for="skip">Skip:</label>
<input id="skip" v-model.lazy="skip" title="No. of entries to skip" type="number" min="0">
<label for="top">Top:</label>
<input id="top" v-model.lazy="top" title="No. of entries to read" type="number" min="0">
</div>
<div v-if="data" class="not-sidebar-main">
<table id='data' class="hovering striped-table condensed">
<thead>
<th v-for="col in columns" :title="col.type" :class="[col.isKey ? 'key' : 'not-key']">{{ col.name }} </th>
</thead>
<tr v-for="row in data" @click="inspectRow" :class="{'highlight': isActiveRow(row)}">
<td v-for="d in row" :title="d">{{ d }}</td>
</tr>
</table>
</div>
<div v-if="error" class="not-sidebar-main error">
Error: {{ error.code ? error.code + ' &ndash; ' + error.message : error.message }}
</div>
<p></p>
<div v-if="rowDetails" class="not-sidebar-sub">
<table id='rowDetails'>
<tr v-for="(key, value) in rowDetails" >
<td class="key">{{ value }}</td>
<td>{{ key }}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -1 +0,0 @@
using from './srv/data-service';

View File

@@ -1,5 +0,0 @@
{
"compilerOptions": {
"checkJs": true
}
}

View File

@@ -1,13 +0,0 @@
{
"name": "@capire/data-viewer",
"version": "0.1.0",
"description": "A generic browser for data",
"dependencies": {
"@sap/cds": ">=5.0.4"
},
"files": [
"app",
"srv",
"index.cds"
]
}

View File

@@ -1,29 +0,0 @@
/**
* Exposes data + entity metadata
*/
@requires:'authenticated-user'
service DataService @( path:'-data' ) {
/**
* Metadata like name and columns/elements
*/
entity Entities {
key name : String;
columns: Composition of many {
name : String;
type : String;
isKey: Boolean;
}
}
/**
* The actual data, organized by column name
*/
entity Data {
record : array of {
column : String;
data : String;
}
}
}

View File

@@ -1,58 +0,0 @@
const cds = require('@sap/cds')
const log = cds.log('data')
class DataService extends cds.ApplicationService { init(){
this.on ('READ', 'Entities', req => {
const { dataSource } = req.req.query
const srvPrefixes = cds.db.model.all('service').map(srv => srv.name+'.')
const dataSourceFilter = dataSource === 'db'
? e => e['@cds.persistence.skip'] !== true // for DB, excl. entities w/o persistence
: e => !!srvPrefixes.find(srvName => e.name.startsWith(srvName)) // only entities reachable from a service
return cds.db.model.all('entity')
.filter (e => req.data && req.data.name ? e.name === req.data.name : true) // honor name filter from request, if any
.filter (e => !e.name.startsWith('DRAFT.')) // exclude synthetic stuff
.filter (e => !e.name.startsWith('DataService.')) // exclude this service
.filter (dataSourceFilter)
.sort((e1, e2) => e1.name.localeCompare(e2.name))
.map(e => {
const columns = Object.entries(e.elements)
.filter(([_, el]) => !(el instanceof cds.Association)) // exclude assocs+compositions
.map(([name, el]) => { return { name, type: el.type, isKey:!!el.key }})
return { name: e.name, columns }
})
})
this.on ('READ', 'Data', async req => {
const { entity: entityName, dataSource: dataSourceName } = req.req.query
if (!entityName) return req.reject(400, `Must provide 'entity' query`)
const entity = cds.db.model.definitions[entityName]
if (!entity) return req.reject(404, 'No such entity: ' + entityName)
const query = SELECT.from(entity)
query.SELECT.limit = req.query.SELECT.limit // forward $skip / $top
const dataSource = findDataSource(dataSourceName, entityName)
const res = await dataSource.run(query)
return res.map((line) => {
const record = Object.entries(line).map(([column, data]) => {return {column, data}})
return { record }
})
})
return super.init()
}}
module.exports = { DataService }
function findDataSource(dataSourceName, entityName) {
for (let srv of Object.values(cds.services)) { // all connected services
if (!srv.name) continue // FIXME intermediate/pending in cds.services ?
if (dataSourceName === srv.name || entityName.startsWith(srv.name+'.')) {
log._debug && log.debug(`using ${srv.name} as data source`)
return srv
}
}
return cds.services.db // fallback
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 67 KiB

View File

@@ -1,172 +0,0 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="347px" height="279px" viewBox="-0.5 -0.5 347 279" content="&lt;mxfile&gt;&lt;diagram id=&quot;QQJxv4aCTC7ZgE7HHOvM&quot; name=&quot;Page-1&quot;&gt;7Vlbb+I4FP41vCLfL4/T7szuy0oj9WGfczEkasCRMYXur98TEufeqpoBCtUGCezv+Hq+z/bBWdDHzfFPF5XZ3zY1xYKg9LigfywI0QLBdwW81oDUrAbWLk9rCHXAU/6vqUEc0H2eml2D1ZC3tvB5OQQTu92axA+wyDl7GBZb2SIdAGW0NoNhVMBTEhVmUuyfPPVZjSoiO/wvk6+z0DMWurbEUfK8dna/bfpbEIq/VZ/avIlCW02/uyxK7aEH0e8L+uis9XVqc3w0ReXaodt+vGFtx+3M1n+kAqkrvETFvpn6gqEkKnNnqkpERJtyQR+28a76gXwBzT7EDlJr30cCEFv7vMtsGQzQStwVPk3ZvwY3w+zLKpmZY7S2WyhSGpdvjDeuQ38GCJzzcMhyb57KKKmqHUB7gGV+U0AOQ3KVH02QU5XfPRufVB5HJ2NRPNrCulPndLUyIkmqQt7ZZ9OzpFLHCLWWoADSTuHFOG+Ob7oct0TC+jAWBu9eoUhTgcimSrM2KG9UdeiUpht5ZD2RqQaLGm2v25Y7fiHRUDxPNz033avcuvwOuFaJmec6Vpzxq3FNrsg1OzfXzrzkBnbWm2c75UalbI5tRWIqxKXYppwPVza6Htv83GwndrMB4m6e7BWvPhVut76H18+cCMTpuZQIsFIDEbArikCcWwTWpcbdw4qPjFrN7u8iUSZeXY5s+WkrXk7JHnNitum3KhqGXFJEu12eDF08cOnUQVC98b882SPnB/mRq3+cntaxJg0x9ltuhbHavUvMYAeDXtbG96LTqfN7zuUzzg2YM0Xk85fhIOY83vTw0+YwvI5bCOuH5AayQxv16Jtq/SB70pIYtURHLdWTnrR0kkA78Q+pQn0xVYipKuhnqoJIueSKtk8Y4Gtn7lknRH9UMkTjJcOifSQbdqPEsmcVgl5KT/qL6Yncl54ofZ/oM+mJkvdlez49hRueLyModl+CYtcRFLueoPBUUL8X9O72ZVnk/8e978a9o2MvLOsrxL145s5yTOETNAKCRLfPoMEpN7P7khaSRpf7m4rQkEFBrsfgzDXkXZ8BQZL9QwC/4f+bOAXGYUXYvS8cVoRRXOAUmLnt/HKSEp+pKAxUS6Q4QVhqKtv7h7B/aAZKkIghypVk7FfDCqSWIComtSJIgIrpsBfFQNZEcy0YJpxfLqqYuU+9bz2F16i3EqdiTWELUlWoqDUS46tzSpYQm3KOGYJgQ5NflZOADQhLySVCmrJxJ3oJOyFjimuFKVJnUhNkuze4dfHuLTn9/h8=&lt;/diagram&gt;&lt;/mxfile&gt;" style="background-color: rgb(26, 26, 26);">
<defs/>
<g>
<path d="M 192 148 L 242 148 L 262 188 L 242 228 L 192 228 L 172 188 Z" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 188px; margin-left: 173px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
bookshop
</b>
</div>
</div>
</div>
</foreignObject>
<text x="217" y="192" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 192 48 L 242 48 L 262 88 L 242 128 L 192 128 L 172 88 Z" fill="#f8cecc" stroke="#b85450" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 88px; margin-left: 173px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
fiori
</b>
</div>
</div>
</div>
</foreignObject>
<text x="217" y="92" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 276 98 L 326 98 L 346 138 L 326 178 L 276 178 L 256 138 Z" fill="#d5e8d4" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 138px; margin-left: 257px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
reviews
</b>
</div>
</div>
</div>
</foreignObject>
<text x="301" y="142" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 109 198 L 159 198 L 179 238 L 159 278 L 109 278 L 89 238 Z" fill="#f5f5f5" stroke="#666666" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 238px; margin-left: 90px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
common
</b>
</div>
</div>
</div>
</foreignObject>
<text x="134" y="242" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 108 98 L 158 98 L 178 138 L 158 178 L 108 178 L 88 138 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 138px; margin-left: 89px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
orders
</b>
</div>
</div>
</div>
</foreignObject>
<text x="133" y="142" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 168.58 217.17 L 174.72 213.47" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 180.5 209.99 L 175.11 218.49 L 174.72 213.47 L 170.47 210.78 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 167.68 117.36 L 174.6 113.24" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 180.4 109.79 L 174.97 118.26 L 174.6 113.24 L 170.36 110.52 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 217 148 L 217 136.99" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 217 130.24 L 221.5 139.24 L 217 136.99 L 212.5 139.24 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 266.32 117.36 L 259.4 113.24" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 253.6 109.79 L 263.64 110.52 L 259.4 113.24 L 259.03 118.26 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 107 1 L 157 1 L 177 41 L 157 81 L 107 81 L 87 41 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 41px; margin-left: 88px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
suppliers
</b>
</div>
</div>
</div>
</foreignObject>
<text x="132" y="45" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 21 53 L 71 53 L 91 93 L 71 133 L 21 133 L 1 93 Z" fill="#e1d5e7" stroke="#9673a6" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 93px; margin-left: 2px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<b>
S/4
</b>
</div>
</div>
</div>
</foreignObject>
<text x="46" y="97" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
S/4
</text>
</switch>
</g>
<path d="M 80.55 72.11 L 89.76 66.54" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 95.53 63.05 L 90.16 71.56 L 89.76 66.54 L 85.5 63.86 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 81.75 111.49 L 89.27 115.38" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 95.26 118.48 L 85.2 118.34 L 89.27 115.38 L 89.33 110.35 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 167.25 60.49 L 173.88 64.16" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 179.79 67.42 L 169.74 67.01 L 173.88 64.16 L 174.09 59.13 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Viewer does not support full SVG 1.1
</text>
</a>
</switch>
</svg>

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,281 +0,0 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="569px" height="428px" viewBox="-0.5 -0.5 569 428" content="&lt;mxfile&gt;&lt;diagram id=&quot;QQJxv4aCTC7ZgE7HHOvM&quot; name=&quot;Page-1&quot;&gt;7Vpdd9o4EP01PJZjWf7SY8jHds9pd3tKt30WtjDayBZHFknor18ployNDCXEELbtyUPsQRpLvndmrgZG8Lp4+kPg5eIjzwgb+V72NII3I98HMPLUP21Z15YYBbUhFzQzgzaGKf1OjNHMy1c0I1VnoOScSbrsGlNeliSVHRsWgj92h8056z51iXPzRG9jmKaYEWfYN5rJRW1N/Hhjf09ovrBPBhGqPymwHWwcVwuc8ceWCd6O4LXgXNZXxdM1Yfrl2fdSz7vb8WmzMEFKecgEv57wgNnK7G2qBis0vPdXf12ZNcq13bjgqzIjeq43gpPHBZVkusSp/vRRQa1sC1kwdQfUZcoLmpqh1T2R6cLc/Evz/NljoG7mvJR3uKBMc+E9YQ9E0hTrKVLw++b9qjczwYzmpboW9audPBChx7IrY5dcr6BSC6Jlru7Dxss1Z1w8bwEmvv7TK+WCflcPx8yusZ742QAXm8VNzfaBuW/5Cm70n7Kbt6jWQ552IgEafFVgEF4QKdZqiJnwDtioMEEBLEceNxQDKKxtiza9LIrY0DpvnG+QVxcG/H4iQIcIk1VFS1JVI72OiOkXPhPqKtdXn7CQJRF7CALORJAWHLMkDEKvlzpzylhr5DxJSZoOBRxIxmEXuhC50Fk429CF3uuRCxzkGrgsWn+WqUqYaju+9zEvZAtR5bwZ5aJ8+yRJWVFeqhlXy+WFpgNG5odkA7Q3G5wwsg+IaxjELjlggF7PjtBhhyVDdVnBG6UJmc0PCd4Mk2Q+UPD6YdwFB/lu5IKepDtE5EY9kbsFCSmzKy1Y1F1GccHL7MuCKnZP1Ad3lFkw1J1RSSDqwnRSACqpyoBd3+3nApdrazWL8+y9WR5qYCOZ1Vi7QFMvgq9EakeZl6Oc5cSC0I9tGzsbfIIwLOlD95F94Bl3nzh9zpc2hoMOTQIQ2pRvfdRrNdPaimvLU5Owd7uqt+i4eqZTs82DGBY7DLteVVJ5EBcW/VmMZt5hpXtOoqFKN+pC0Rv8pyrbiQPNdJWmSm/d4VTyvfj8/7V3j9R25Phbam8/ilwmoGjcUwh8bwD1jRwyfOPifs519uuV3yqAtSq7pBC+GPUNPXi+MLYtiRZ0t8WS8TXpR+4LLUi1IET+Bq8fvKQn8k4GHniRApsxnt6rnWdYIWiB2ggxryvE/GOF2NG18KWSCrqKKj6fouoK7xDEx+qpHzgaTk1ZCdqiy0dVsHFOLkxNXcZZCnrADeVTnaWA28DaF8qXephqHZMOO1wdF/nhW0a+H8MuTyA8MvQB2u9owNB3u2xTIh7oDn2m1duldUcv4oh1Xm3mNr9+pfKO3CCvaXyWKAfbwg5GxxZ4tOUqCrdWM2Ccv6wnV/KS7CPJxdQPviTlYNXDJsO3acWByB97CYp0Uw6EHoJ+hxx+hI7jGYjgOAAejGMYJgHyoqTrF5yuuLhtOltAemrL1QOmDM8oo3L9u8K4FeasB0i3i/crVRjb/OiUmPMJSeDUmODIGgO8YL+jAYPd7fVNcHqvcFdGoynd46Rtt37AM8I+8YpK/a2sYhSXkhcaavtDEq/NL6/VuU0VkiqldDi1o4vr0EZ5X+qVFE+5/kXPuFA8Xi3HBRb6X7oSbD0Rz2lmT2vplelGcInNrr09bak5o8uvZnsDZJd3YOv7mtDryS8QuPnFHyC/+G538R/db1Cmr5Q8/lQ8abLWaXliM97APEm2G5nn5YnbyGz/duMGS/wzUaVRxyelSqOsB6aKH6A3ZIrbw3SYYaFJ14wqNSngj6XkrNadH2aNQdW0/FmN/r2Syg0x9qoWN/4YQRSiGIIwgJEPwTaAu085h5+UBsAKJF2sAIpdrJALFQhfDJW63fzusxYWm1/Pwtv/AA==&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/>
<g>
<rect x="1" y="1" width="195" height="122" fill="#ffffff" stroke="#828282" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)rotate(-90 11 13)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 107px; height: 1px; padding-top: 13px; margin-left: -96px;">
<div style="box-sizing: border-box; font-size: 0; text-align: right; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #4D4D4D; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
S/4 HANA
</div>
</div>
</div>
</foreignObject>
<text x="11" y="25" fill="#4D4D4D" font-family="Helvetica" font-size="12px" text-anchor="end" font-weight="bold">
S/4 HANA
</text>
</switch>
</g>
<rect x="42.5" y="40" width="120" height="50" rx="7.5" ry="7.5" fill="#f8cecc" stroke="#b85450" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 65px; margin-left: 44px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Business
<br/>
Partner
</div>
</div>
</div>
</foreignObject>
<text x="103" y="69" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Business...
</text>
</switch>
</g>
<rect x="221" y="1" width="347" height="349" fill="#ffffff" stroke="#828282" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 331px; height: 1px; padding-top: 15px; margin-left: 230px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #4D4D4D; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<b>
Incident Mgmt
</b>
<br/>
Extension App
</div>
</div>
</div>
</foreignObject>
<text x="230" y="27" fill="#4D4D4D" font-family="Helvetica" font-size="12px">
Incident Mgmt...
</text>
</switch>
</g>
<rect x="418" y="73" width="115" height="50" rx="7.5" ry="7.5" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 113px; height: 1px; padding-top: 98px; margin-left: 419px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Incidents
</div>
</div>
</div>
</foreignObject>
<text x="476" y="102" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Incidents
</text>
</switch>
</g>
<path d="M 475.5 182 L 475.5 142.97" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 481.5 182 L 475.5 170 L 469.5 182" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 475.5 124.97 L 480.79 133.97 L 475.5 142.97 L 470.21 133.97 Z" fill="#6c8ebf" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<rect x="255" y="73" width="120" height="50" rx="7.5" ry="7.5" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 98px; margin-left: 256px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Customers
</div>
</div>
</div>
</foreignObject>
<text x="315" y="102" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Customers
</text>
</switch>
</g>
<rect x="1" y="147" width="196.5" height="202" fill="#ffffff" stroke="#828282" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)rotate(-90 11 159)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 187px; height: 1px; padding-top: 159px; margin-left: -176px;">
<div style="box-sizing: border-box; font-size: 0; text-align: right; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #4D4D4D; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
SuccessFactors
</div>
</div>
</div>
</foreignObject>
<text x="11" y="171" fill="#4D4D4D" font-family="Helvetica" font-size="12px" text-anchor="end" font-weight="bold">
SuccessFactors
</text>
</switch>
</g>
<rect x="42.5" y="184" width="120" height="50" rx="7.5" ry="7.5" fill="#f8cecc" stroke="#b85450" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 209px; margin-left: 44px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Workforce
<br/>
Person
</div>
</div>
</div>
</foreignObject>
<text x="103" y="213" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Workforce...
</text>
</switch>
</g>
<rect x="42.5" y="267" width="120" height="50" rx="7.5" ry="7.5" fill="#f8cecc" stroke="#b85450" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 292px; margin-left: 44px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Employee
<br/>
Timesheet
</div>
</div>
</div>
</foreignObject>
<text x="103" y="296" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Employee...
</text>
</switch>
</g>
<path d="M 162.5 74.32 L 238.96 86.19" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/>
<path d="M 252.79 88.34 L 237.88 93.11 L 240.03 79.27 Z" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<rect x="418" y="182" width="115" height="50" rx="7.5" ry="7.5" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 113px; height: 1px; padding-top: 207px; margin-left: 419px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Messages
</div>
</div>
</div>
</foreignObject>
<text x="476" y="211" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Messages
</text>
</switch>
</g>
<path d="M 418 98 L 394.97 98" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 418 92 L 406 98 L 418 104" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 376.97 98 L 385.97 92.71 L 394.97 98 L 385.97 103.29 Z" fill="#6c8ebf" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<rect x="255" y="184" width="120" height="50" rx="7.5" ry="7.5" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 209px; margin-left: 256px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Service
<br/>
Worker
</div>
</div>
</div>
</foreignObject>
<text x="315" y="213" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Service...
</text>
</switch>
</g>
<path d="M 162.5 209 L 238.76 209" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/>
<path d="M 252.76 209 L 238.76 216 L 238.76 202 Z" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 354.83 181.46 L 439.35 123" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 358.91 171.95 L 352.99 182.73 L 365.16 180.99" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<rect x="255" y="267" width="120" height="50" rx="7.5" ry="7.5" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 292px; margin-left: 256px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Worker
<br/>
Availability
</div>
</div>
</div>
</foreignObject>
<text x="315" y="296" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Worker...
</text>
</switch>
</g>
<path d="M 162.5 292 L 238.76 292" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/>
<path d="M 252.76 292 L 238.76 299 L 238.76 285 Z" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 37 407 C 37 401.48 41.48 397 47 397 L 92.5 397 C 98.02 397 102.5 392.52 102.5 387 C 102.5 392.52 106.98 397 112.5 397 L 158 397 C 163.52 397 168 401.48 168 407" fill="none" stroke="#b85450" stroke-width="2" stroke-miterlimit="10" transform="translate(0,397)scale(1,-1)translate(0,-397)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 414px; margin-left: 103px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Backend Services
</div>
</div>
</div>
</foreignObject>
<text x="103" y="426" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Backend Services
</text>
</switch>
</g>
<path d="M 249.5 407 C 249.5 401.48 253.98 397 259.5 397 L 305 397 C 310.52 397 315 392.52 315 387 C 315 392.52 319.48 397 325 397 L 370.5 397 C 376.02 397 380.5 401.48 380.5 407" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" transform="translate(0,397)scale(1,-1)translate(0,-397)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 414px; margin-left: 315px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Usage Views
</div>
</div>
</div>
</foreignObject>
<text x="315" y="426" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Usage Views
</text>
</switch>
</g>
<path d="M 410 407 C 410 401.48 414.48 397 420 397 L 465.5 397 C 471.02 397 475.5 392.52 475.5 387 C 475.5 392.52 479.98 397 485.5 397 L 531 397 C 536.52 397 541 401.48 541 407" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" transform="translate(0,397)scale(1,-1)translate(0,-397)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 414px; margin-left: 476px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Extension Data
</div>
</div>
</div>
</foreignObject>
<text x="476" y="426" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Extension Data
</text>
</switch>
</g>
<path d="M 350 80.94 C 350 79.32 354.25 78 359.5 78 C 362.02 78 364.44 78.31 366.22 78.86 C 368 79.41 369 80.16 369 80.94 L 369 90.06 C 369 91.68 364.75 93 359.5 93 C 354.25 93 350 91.68 350 90.06 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 369 80.94 C 369 82.56 364.75 83.88 359.5 83.88 C 354.25 83.88 350 82.56 350 80.94" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Viewer does not support full SVG 1.1
</text>
</a>
</switch>
</svg>

Before

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 40 KiB

2
fiori/.env Normal file
View File

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

View File

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

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

@@ -1,11 +0,0 @@
# 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

@@ -1,141 +0,0 @@
{
"_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": {
"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

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

View File

@@ -1,5 +1,4 @@
using { AdminService } from '@capire/bookstore';
using from '../common'; // to help UI linter get the complete annotations
using { AdminService, sap.capire.bookshop } from '../../db/schema';
////////////////////////////////////////////////////////////////////////////
//
@@ -40,6 +39,27 @@ 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}'},
]
},
}
);
////////////////////////////////////////////////////////////
@@ -64,15 +84,11 @@ annotate AdminService.Books.texts with @(
// Add Value Help for Locales
annotate AdminService.Books.texts {
locale @(
ValueList.entity:'Languages', Common.ValueListWithFixedValues, //show as drop down, not a dialog
)
locale @ValueList:{entity:'Languages',type:#fixed}
}
// In addition we need to expose Languages through AdminService as a target for ValueList
// In addition we need to expose Languages and Books.texts through AdminService
using { sap } from '@sap/cds/common';
extend service AdminService {
@readonly entity Languages as projection on sap.common.Languages;
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; }

View File

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

View File

@@ -1,14 +1,14 @@
{
"_version": "1.8.0",
"sap.app": {
"id": "books",
"id": "admin",
"type": "application",
"title": "Manage Books",
"description": "Sample Application",
"i18n": "i18n/i18n.properties",
"dataSources": {
"AdminService": {
"uri": "admin/",
"uri": "/admin/",
"type": "OData",
"settings": {
"odataVersion": "4.0"
@@ -73,7 +73,6 @@
"options": {
"settings" : {
"entitySet" : "Books",
"initialLoad": true,
"navigation" : {
"Books" : {
"detail" : {

View File

@@ -1,168 +0,0 @@
{
"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": "BrowseGenres",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Browse Genres (OData v2)",
"targetURL": "#Genres-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"
}
},
"BrowseGenres": {
"semanticObject": "Genres",
"action": "display",
"title": "Browse Genres",
"signature": {
"parameters": {
"Genre.ID": {
"renameTo": "ID"
}
},
"additionalParameters": "ignored"
},
"resolutionResult": {
"applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=genres",
"url": "/genres/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"
}
}
}
}
}
}
}
}

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

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

View File

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

View File

@@ -1,60 +1,28 @@
{
"_version": "1.28.0",
"_version": "1.8.0",
"sap.app": {
"id": "bookshop",
"type": "application",
"title": "Browse Books",
"description": "Sample Application",
"i18n": "i18n/i18n.properties",
"applicationVersion": {
"version": "1.0.0"
},
"dataSources": {
"CatalogService": {
"uri": "browse/",
"uri": "/browse/",
"type": "OData",
"settings": {
"odataVersion": "4.0"
}
}
},
"sourceTemplate": {
"-sourceTemplate": {
"id": "ui5template.basicSAPUI5ApplicationProject",
"-id": "ui5template.smartTemplate",
"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
}
}
}
"-version": "1.40.12"
}
},
"sap.ui5": {
"dependencies": {
"minUI5Version": "1.81.0",
"libs": {
"sap.fe.templates": {}
}
@@ -100,7 +68,6 @@
"options": {
"settings": {
"entitySet": "Books",
"initialLoad": true,
"navigation": {
"Books": {
"detail": {
@@ -130,12 +97,7 @@
},
"sap.ui": {
"technology": "UI5",
"fullWidth": false,
"deviceTypes":{
"desktop": true,
"tablet": true,
"phone": true
}
"fullWidth": false
},
"sap.fiori": {
"registrationIds": [],

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