Compare commits
1 Commits
uom
...
fk-constra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5acea58f2 |
2425
.deploy/app-router/package-lock.json
generated
2425
.deploy/app-router/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "approuter",
|
|
||||||
"dependencies": {
|
|
||||||
"@sap/approuter": "^20.0.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "node node_modules/@sap/approuter/approuter.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../bookshop/app/vue
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../orders/app/orders
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../reviews/app/vue
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
"welcomeFile": "app/bookshop/index.html",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"source": "^/app/(.*)$",
|
|
||||||
"target": "$1",
|
|
||||||
"localDir": "resources",
|
|
||||||
"cacheControl": "no-cache, no-store, must-revalidate"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "^/admin/(.*)$",
|
|
||||||
"target": "/admin/$1",
|
|
||||||
"destination": "bookstore-api",
|
|
||||||
"csrfProtection": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "^/browse/(.*)$",
|
|
||||||
"target": "/browse/$1",
|
|
||||||
"destination": "bookstore-api",
|
|
||||||
"csrfProtection": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "^/user/(.*)$",
|
|
||||||
"target": "/user/$1",
|
|
||||||
"destination": "bookstore-api",
|
|
||||||
"csrfProtection": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "^/odata/v4/orders/(.*)$",
|
|
||||||
"target": "/odata/v4/orders/$1",
|
|
||||||
"destination": "orders-api",
|
|
||||||
"csrfProtection": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "^/reviews/(.*)$",
|
|
||||||
"target": "/reviews/$1",
|
|
||||||
"destination": "reviews-api",
|
|
||||||
"csrfProtection": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
27
.eslintrc
Normal file
27
.eslintrc
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true,
|
||||||
|
"es6": true,
|
||||||
|
"jest": true,
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2018
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"SELECT": true,
|
||||||
|
"INSERT": true,
|
||||||
|
"UPDATE": true,
|
||||||
|
"DELETE": true,
|
||||||
|
"CREATE": true,
|
||||||
|
"DROP": true,
|
||||||
|
"cds": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-console": "off",
|
||||||
|
"require-atomic-updates": "off",
|
||||||
|
"require-await":"warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: This channel is CLOSED.
|
|
||||||
about: Use SAP community instead
|
|
||||||
url: https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce
|
|
||||||
10
.github/ISSUE_TEMPLATE/question--feedback-or-bug-.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/question--feedback-or-bug-.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
name: This channel is CLOSED.
|
||||||
|
about: Use our community at https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Please use our community on https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce
|
||||||
20
.github/dependabot.yml
vendored
20
.github/dependabot.yml
vendored
@@ -1,20 +0,0 @@
|
|||||||
version: 2
|
|
||||||
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: npm
|
|
||||||
directory: /
|
|
||||||
versioning-strategy: increase-if-necessary
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
groups:
|
|
||||||
production-dependencies:
|
|
||||||
dependency-type: "production"
|
|
||||||
development-dependencies:
|
|
||||||
dependency-type: "development"
|
|
||||||
ignore:
|
|
||||||
- dependency-name: "chai"
|
|
||||||
# chai 5 doesn't work atm w/ cds.test, TODO fix that in cds.test
|
|
||||||
versions: ["5.x"]
|
|
||||||
- dependency-name: "chai-as-promised"
|
|
||||||
# chai-as-promised 8 doesn't work atm w/ cds.test, TODO fix that in cds.test
|
|
||||||
versions: ["8.x"]
|
|
||||||
24
.github/workflows/node.js.yml
vendored
24
.github/workflows/node.js.yml
vendored
@@ -5,34 +5,24 @@ name: CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ master ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches: [ master ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [22.x, 20.x, 18.x]
|
node-version: [12.x, 14.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: npm ci
|
- run: npm install
|
||||||
- run: npm test
|
- run: npm test
|
||||||
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22.x
|
|
||||||
- run: npm ci
|
|
||||||
- run: npm run lint
|
|
||||||
|
|||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -12,14 +12,6 @@ target/
|
|||||||
*.mtar
|
*.mtar
|
||||||
connection.properties
|
connection.properties
|
||||||
default-env.json
|
default-env.json
|
||||||
.cdsrc-private.json
|
|
||||||
packages/messageBox
|
packages/messageBox
|
||||||
reviews/msg-box
|
reviews/msg-box
|
||||||
reviews/db/test.db
|
reviews/db/test.db
|
||||||
|
|
||||||
*.openapi3.json
|
|
||||||
*.sqlite
|
|
||||||
*.db
|
|
||||||
|
|
||||||
@types/
|
|
||||||
@cds-models/
|
|
||||||
|
|||||||
1
.registry/.gitignore
vendored
Normal file
1
.registry/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.tgz
|
||||||
71
.registry/server.js
Normal file
71
.registry/server.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
const { exec } = require ('child_process')
|
||||||
|
const express = require ('express')
|
||||||
|
const fs = require ('fs')
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
const { PORT=4444 } = process.env
|
||||||
|
const [,,port=PORT] = process.argv
|
||||||
|
process.chdir(__dirname)
|
||||||
|
|
||||||
|
app.use('/-/:tarball', (req,res,next) => {
|
||||||
|
console.debug ('GET', req.params)
|
||||||
|
try {
|
||||||
|
const { tarball } = req.params
|
||||||
|
const [, pkg ] = /^capire-(\w+)/.exec(tarball)
|
||||||
|
fs.lstat(tarball,(err => {
|
||||||
|
if (err) exec(`npm pack ../${pkg}`,next)
|
||||||
|
else next()
|
||||||
|
}))
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
res.sendStatus(500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use('/-', express.static(__dirname))
|
||||||
|
|
||||||
|
app.get('/*', (req,res)=>{
|
||||||
|
const url = decodeURIComponent(req.url)
|
||||||
|
console.debug ('GET',url)
|
||||||
|
try {
|
||||||
|
const [, capire, pkg ] = /^\/(@capire)\/(\w+)/.exec(url)
|
||||||
|
const package = require (`${capire}/${pkg}/package.json`)
|
||||||
|
const tarball = `capire-${pkg}-${package.version}.tgz`
|
||||||
|
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
|
||||||
|
res.json({
|
||||||
|
"name": package.name,
|
||||||
|
"dist-tags": {
|
||||||
|
"latest": package.version
|
||||||
|
},
|
||||||
|
"versions": {
|
||||||
|
[package.version]: {
|
||||||
|
"name": package.name,
|
||||||
|
"version": package.version,
|
||||||
|
"dist": {
|
||||||
|
"tarball": `http://localhost:${port}/-/${tarball}`
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
res.sendStatus(404)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(port, ()=>{
|
||||||
|
console.log (`npm set @capire:registry=http://localhost:${port}`)
|
||||||
|
console.log (`@capire registry listening on http://localhost:${port}`)
|
||||||
|
exec(`npm set @capire:registry=http://localhost:${port}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
const _exit = ()=>{
|
||||||
|
console.log ('\nnpm conf rm @capire:registry')
|
||||||
|
exec('npm conf rm @capire:registry')
|
||||||
|
exec('rm *.tgz')
|
||||||
|
process.exit()
|
||||||
|
}
|
||||||
|
process.on ('SIGTERM',_exit)
|
||||||
|
process.on ('SIGHUP',_exit)
|
||||||
|
process.on ('SIGINT',_exit)
|
||||||
|
process.on ('SIGUSR2',_exit)
|
||||||
@@ -24,6 +24,6 @@ Disclaimer: The code in this project may include calls to APIs (“API Calls”)
|
|||||||
you any rights to use or access any SAP External Product, or provide any third
|
you any rights to use or access any SAP External Product, or provide any third
|
||||||
parties the right to use of access any SAP External Product, through API Calls.
|
parties the right to use of access any SAP External Product, through API Calls.
|
||||||
|
|
||||||
Files: *.*
|
Files: *
|
||||||
Copyright: 2019-2025 SAP SE or an SAP affiliate company and cap-cloud-samples
|
Copyright: 2019-2020 SAP SE or an SAP affiliate company and cap-cloud-samples
|
||||||
License: Apache-2.0
|
License: Apache-2.0
|
||||||
|
|||||||
109
.tours/db-native.tour
Normal file
109
.tours/db-native.tour
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://aka.ms/codetour-schema",
|
||||||
|
"title": "Database Functions",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"title": "Introduction",
|
||||||
|
"description": "### Database Functions in CDS Models\n\nIn this tour, you'll learn how to add database-specific functions to CDS models in your application."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "bookshop/db/schema.cds",
|
||||||
|
"description": "#### Basic Schema\n\nWe want to add two fields to the `Authors` entity, one for the author's age and one for the span of years that she or he lived.\n\nThese two fields can be computed out of the existing `dateOfBirth` and `dateOfDeath` fields.",
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 19,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 21,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Base fields in Author"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "bookshop/srv/admin-service.cds",
|
||||||
|
"description": "This is how the `Authors` entity gets exposed in an OData or REST service.\n\nIn the next step, you'll see how we extend this projection.",
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 4,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Authors service"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "fiori/db/sqlite/index.cds",
|
||||||
|
"description": "#### SQLite Implementation\n\nHere's the first implementation for SQLite. It computes the two fields `age` and `lifetime` through SQLite's [strftime](https://sqlite.org/lang_datefunc.html) function.\n\nThrough the [`extend projection`](https://cap.cloud.sap/docs/cds/cdl#extend-view) clause you can add additional fields to projection entities. These are deployed as database views, which is why we can integrate the database functions in the first place.\n",
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 7,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 11,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "SQLite implementation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "fiori/db/hana/index.cds",
|
||||||
|
"description": "#### SAP HANA Implementation\n\nThis is the second implementation for SAP HANA. It computes the same two fields `age` and `lifetime` through the [YEARS_BETWEEN](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/7c0d2c161ea34def86de3f5eadd6a0af.html) and [YEAR](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/20f5fac6751910148dabd3c6821f907d.html) functions of SAP HANA.\n\n#### File Layout and Code Structure\n\nNote the path of the `.cds` file we are in: it's in a subfolder of `db`, so that it's _not_ automatically picked up when we start the application. The same is true for the SQLite implementation: it's in a separate `db/sqlite/` folder as well. In the next step, you'll see how these files are loaded.\n\nAlso, we choose to implement all of that as an extension of the original bookshop here in the _fiori_ package. See the first [CAP Samples] code tour for more details on the different packages of this repository.",
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 7,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 11,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "SAP HANA implementation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "fiori/package.json",
|
||||||
|
"description": "#### Configuration\n\nThe `cds` section in `package.json` is a place to configure which of the `db/sqlite` and `db/hana` folders are used for which database.\nWe use [Node.js profiles](https://cap.cloud.sap/docs/node.js/cds-env#profiles) to separate the configuration.\nIn the `development` profile, you can see that `db/sqlite` is set as the model, while the `db/hana` folder is configured in the `production` profile.",
|
||||||
|
"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": 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": 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": 68,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 67,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 73,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Add Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
136
.tours/samples.tour
Normal file
136
.tours/samples.tour
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://aka.ms/codetour-schema",
|
||||||
|
"title": "CAP Samples",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"title": "Welcome",
|
||||||
|
"file": "README.md",
|
||||||
|
"description": "### Welcome to CAP Samples!\n\nThis tour leads you through a collection of samples for the [SAP Cloud Application Programming Model (CAP)](https://cap.cloud.sap).\nYou will learn which features of the programming model are demonstrated in which sample.\n\nLet's start!",
|
||||||
|
"line": 2,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 3,
|
||||||
|
"character": 108
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "hello/world.cds",
|
||||||
|
"description": "### Hello World!\n\nThis is a simplistic [Hello World](https://cap.cloud.sap/docs/get-started/hello-world) service using [CDS](https://cap.cloud.sap/docs/cds/) and [cds.services](https://cap.cloud.sap/docs/node.js/api#services-api).",
|
||||||
|
"line": 2,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 4,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Hello World!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "bookshop/db/schema.cds",
|
||||||
|
"description": "### A Bookshop!\n\nIntroduces:\n- [Project Setup](https://cap.cloud.sap/docs/get-started/) and [Layouts](https://cap.cloud.sap/docs/get-started/projects)\n- [Domain Modeling](https://cap.cloud.sap/docs/guides/domain-models)\n- [Defining Services](https://cap.cloud.sap/docs/guides/providing-services)\n- [Generic Providers](https://cap.cloud.sap/docs/guides/generic-providers)\n- [Adding Custom Logic](https://cap.cloud.sap/docs/guides/service-impl)\n- [Using Databases](https://cap.cloud.sap/docs/guides/databases)\n",
|
||||||
|
"line": 1,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 32,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Bookshop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "common/index.cds",
|
||||||
|
"description": "### Extend and Reuse\n\nShowcases how to extend [@sap/cds/common](https://cap.cloud.sap/docs/cds/common) thereby covering:\n- Building [extension packages](https://cap.cloud.sap/docs/guides/domain-models#aspects-extensibility)\n- Providing [reuse packages](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content)\n- [Verticalization](https://cap.cloud.sap/docs/cds/common#adapting-to-your-needs)\n- Using [Aspects](https://cap.cloud.sap/docs/cds/cdl#aspects)\n- Used in the [fiori app sample](#fiori)\n",
|
||||||
|
"line": 1,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 46,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Common"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "orders/db/schema.cds",
|
||||||
|
"description": "### Compositions and Serving Documents\n\nA standalone orders management service, demonstrating:\n- Using [Compositions](https://cap.cloud.sap/docs/cds/cdl#compositions) in [Domain Models](https://cap.cloud.sap/docs/guides/domain-models), along with\n- [Serving deeply nested documents](https://cap.cloud.sap/docs/guides/generic-providers#serving-structured-data)\n",
|
||||||
|
"line": 1,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 27,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Orders"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "reviews/db/schema.cds",
|
||||||
|
"description": "### More Modularity\n\nShows how to implement a modular service to manage product reviews, including:\n- Consuming other services synchronously and asynchronously\n- Serving requests synchronously\n- Emitting events asynchronously\n- Grow as you go, with:\n- Mocking app services\n- Running service meshes\n- Late-cut Micro Services\n- As well as managed data, input validations, and authorization\n",
|
||||||
|
"line": 1,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 39,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Reviews"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "fiori/app/index.cds",
|
||||||
|
"description": "### Annotations for SAP Fiori Elements\n\nA [composite app, reusing and combining](https://cap.cloud.sap/docs/guides/verticalize) these packages:\n - [@capire/bookshop](bookshop)\n - [@capire/reviews](reviews)\n - [@capire/orders](orders)\n - [@capire/common](common)\n\n[Adds a SAP Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookshop, thereby introducing to:\n - [OData Annotations](https://cap.cloud.sap/docs/guides/fiori#adding-odata-annotations) in `.cds` files\n - Support for [Fiori Draft](https://cap.cloud.sap/docs/guides/fiori#draft)\n - Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help)\n - Serving SAP Fiori apps locally\n\n[The Vue.js app](bookshop/app/vue) imported from bookshop is served as well.\n",
|
||||||
|
"line": 1,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 1,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 13,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Fiori"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "package.json",
|
||||||
|
"description": "### All-in-one Monorepo\n\nEach sample sub directory essentially is a standard npm package, some with standard npm dependencies to other samples. The root folder's [package.json](package.json) has local links to the sub folders, such that an `npm install` populates a local `node_modules` folder acts like a local npm registry to the individual sample packages.\n",
|
||||||
|
"line": 8,
|
||||||
|
"selection": {
|
||||||
|
"start": {
|
||||||
|
"line": 8,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 15,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Packages"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isPrimary": true,
|
||||||
|
"description": "Overview of CAP Samples for Node.js"
|
||||||
|
}
|
||||||
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
@@ -4,11 +4,15 @@
|
|||||||
|
|
||||||
// List of extensions which should be recommended for users of this workspace.
|
// List of extensions which should be recommended for users of this workspace.
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"qwtel.sqlite-viewer",
|
|
||||||
"sapse.vscode-cds",
|
"sapse.vscode-cds",
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
"mechatroner.rainbow-csv",
|
"mechatroner.rainbow-csv",
|
||||||
"humao.rest-client",
|
"humao.rest-client",
|
||||||
|
"alexcvzz.vscode-sqlite",
|
||||||
|
"hbenl.vscode-mocha-test-adapter",
|
||||||
|
"sdras.night-owl",
|
||||||
|
"vsls-contrib.codetour"
|
||||||
],
|
],
|
||||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||||
"unwantedRecommendations": [
|
"unwantedRecommendations": [
|
||||||
|
|||||||
20
.vscode/launch.json
vendored
20
.vscode/launch.json
vendored
@@ -13,7 +13,7 @@
|
|||||||
"<node_internals>/**",
|
"<node_internals>/**",
|
||||||
"**/node_modules/**",
|
"**/node_modules/**",
|
||||||
"**/cds/lib/lazy.js",
|
"**/cds/lib/lazy.js",
|
||||||
"**/cds/lib/req/cds-context.js",
|
"**/cds/lib/req/cls.js",
|
||||||
"**/odata-v4/okra/**"
|
"**/odata-v4/okra/**"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -26,24 +26,10 @@
|
|||||||
"<node_internals>/**",
|
"<node_internals>/**",
|
||||||
"**/node_modules/**",
|
"**/node_modules/**",
|
||||||
"**/cds/lib/lazy.js",
|
"**/cds/lib/lazy.js",
|
||||||
"**/cds/lib/req/cds-context.js",
|
"**/cds/lib/req/cls.js",
|
||||||
"**/odata-v4/okra/**"
|
"**/odata-v4/okra/**"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
{
|
|
||||||
"name": "Debug Mocha Tests",
|
|
||||||
"type": "node",
|
|
||||||
"request": "attach",
|
|
||||||
"port": 9229,
|
|
||||||
"continueOnAttach": true,
|
|
||||||
"skipFiles": [
|
|
||||||
"<node_internals>/**",
|
|
||||||
"**/node_modules/**",
|
|
||||||
"**/cds/lib/lazy.js",
|
|
||||||
"**/cds/lib/req/cds-context.js",
|
|
||||||
"**/odata-v4/okra/**",
|
|
||||||
]
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
|
|||||||
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
"**/node_modules": true,
|
".reuse/**": true,
|
||||||
"LICENSES": true,
|
"**/.gitignore": true,
|
||||||
".reuse": true
|
"**/.vscode": true,
|
||||||
|
"LICENSES/**": true
|
||||||
},
|
},
|
||||||
"debug.javascript.terminalOptions": {
|
"debug.javascript.terminalOptions": {
|
||||||
"skipFiles": [
|
"skipFiles": [
|
||||||
"<node_internals>/**",
|
"<node_internals>/**",
|
||||||
"**/node_modules/**",
|
"**/node_modules/**",
|
||||||
"**/cds/lib/lazy.js",
|
"**/cds/lib/lazy.js",
|
||||||
"**/cds/lib/req/cds-context.js",
|
"**/cds/lib/req/cls.js",
|
||||||
"**/odata-v4/okra/**"
|
"**/odata-v4/okra/**"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"jest.jestCommandLine": "npx jest"
|
|
||||||
}
|
}
|
||||||
|
|||||||
86
README.md
Normal file
86
README.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# Welcome to cap/samples
|
||||||
|
|
||||||
|
Find here a collection of samples for the [SAP Cloud Application Programming Model](https://cap.cloud.sap) organized in a simplistic [monorepo setup](samples.md#all-in-one-monorepo). → See [**Overview** of contained samples](samples.md)
|
||||||
|
|
||||||
|

|
||||||
|
[](https://api.reuse.software/info/github.com/SAP-samples/cloud-cap-samples)
|
||||||
|
|
||||||
|
### Preliminaries
|
||||||
|
|
||||||
|
1. Install [**@sap/cds-dk**](https://cap.cloud.sap/docs/get-started/) globally:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i -g @sap/cds-dk
|
||||||
|
```
|
||||||
|
|
||||||
|
2. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/tools#vscode)
|
||||||
|
|
||||||
|
### Download
|
||||||
|
|
||||||
|
If you'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
|
||||||
|
cd samples
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
In the samples folder run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run
|
||||||
|
|
||||||
|
With that you're ready to run the samples, for example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cds watch bookshop
|
||||||
|
```
|
||||||
|
|
||||||
|
After that open this link in your browser: [http://localhost:4004](http://localhost:4004)
|
||||||
|
|
||||||
|
When asked to log in, type `alice` as user and leave the password field blank, which is the [default user](https://cap.cloud.sap/docs/node.js/authentication#mocked).
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
Run the provided tests with [_jest_](http://jestjs.io) or [_mocha_](http://mochajs.org), for example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx jest
|
||||||
|
```
|
||||||
|
> While mocha is a bit smaller and faster, jest runs tests in parallel and isolation, which allows to run all tests.
|
||||||
|
|
||||||
|
|
||||||
|
### Serve `npm`
|
||||||
|
|
||||||
|
We've included a simple npm registry mock, which allows you to do an `npm install @capire/<package>` locally. Use it as follows:
|
||||||
|
|
||||||
|
1. Start the @capire registry:
|
||||||
|
```sh
|
||||||
|
npm run registry
|
||||||
|
```
|
||||||
|
> While running this will have `@capire:registry=http://localhost:4444` set with npmrc.
|
||||||
|
|
||||||
|
2. Install one of the @capire packages wherever you like, for example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm add @capire/common @capire/bookshop
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Code Tours
|
||||||
|
|
||||||
|
Take one of the [guided tours](.tours) in VS Code through our CAP samples and learn which CAP features are showcased by the different parts of the repository. Just install the [CodeTour extension](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour) for VS Code. We'll add more code tours in the future. Stay tuned!
|
||||||
|
|
||||||
|
## Get Support
|
||||||
|
|
||||||
|
Check out the documentation at [https://cap.cloud.sap](https://cap.cloud.sap). <br>
|
||||||
|
In case you've a question, find a bug, or otherwise need support, use our [community](https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce) to get more visibility.
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE.txt) file.
|
||||||
2
bookshop/app/index.cds
Normal file
2
bookshop/app/index.cds
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// Incorporate pre-build extensions from...
|
||||||
|
using from '@capire/common';
|
||||||
@@ -3,15 +3,14 @@ const $ = sel => document.querySelector(sel)
|
|||||||
const GET = (url) => axios.get('/browse'+url)
|
const GET = (url) => axios.get('/browse'+url)
|
||||||
const POST = (cmd,data) => axios.post('/browse'+cmd,data)
|
const POST = (cmd,data) => axios.post('/browse'+cmd,data)
|
||||||
|
|
||||||
const books = Vue.createApp ({
|
const books = new Vue ({
|
||||||
|
|
||||||
data() {
|
el:'#app',
|
||||||
return {
|
|
||||||
|
data: {
|
||||||
list: [],
|
list: [],
|
||||||
book: undefined,
|
book: undefined,
|
||||||
order: { quantity:1, succeeded:'', failed:'' },
|
order: { amount:1, succeeded:'', failed:'' }
|
||||||
user: undefined
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -19,7 +18,7 @@ const books = Vue.createApp ({
|
|||||||
search: ({target:{value:v}}) => books.fetch(v && '&$search='+v),
|
search: ({target:{value:v}}) => books.fetch(v && '&$search='+v),
|
||||||
|
|
||||||
async fetch (etc='') {
|
async fetch (etc='') {
|
||||||
const {data} = await GET(`/ListOfBooks?$expand=genre($select=name),currency($select=symbol)${etc}`)
|
const {data} = await GET(`/ListOfBooks?$expand=genre,currency${etc}`)
|
||||||
books.list = data.value
|
books.list = data.value
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -27,63 +26,23 @@ const books = Vue.createApp ({
|
|||||||
const book = books.book = books.list [eve.currentTarget.rowIndex-1]
|
const book = books.book = books.list [eve.currentTarget.rowIndex-1]
|
||||||
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`)
|
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`)
|
||||||
Object.assign (book, res.data)
|
Object.assign (book, res.data)
|
||||||
books.order = { quantity:1 }
|
books.order = { amount:1 }
|
||||||
setTimeout (()=> $('form > input').focus(), 111)
|
setTimeout (()=> $('form > input').focus(), 111)
|
||||||
},
|
},
|
||||||
|
|
||||||
async submitOrder () {
|
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 {
|
try {
|
||||||
const res = await POST(`/submitOrder`, { quantity, book: book.ID })
|
const res = await POST(`/submitOrder`, { amount, book: book.ID })
|
||||||
book.stock = res.data.stock
|
book.stock = res.data.stock
|
||||||
books.order = { quantity, succeeded: `Successfully ordered ${quantity} item(s).` }
|
books.order = { amount, succeeded: `Successfully orderd ${amount} item(s).` }
|
||||||
} catch (e) {
|
} 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
|
|
||||||
})
|
})
|
||||||
|
|
||||||
axios.interceptors.request.use(csrfToken)
|
// initially fill list of books
|
||||||
function csrfToken (request) {
|
books.fetch()
|
||||||
if (request.method === 'head' || request.method === 'get') return request
|
|
||||||
if ('csrfToken' in document) {
|
|
||||||
request.headers['x-csrf-token'] = document.csrfToken
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
return fetchToken().then(token => {
|
|
||||||
document.csrfToken = token
|
|
||||||
request.headers['x-csrf-token'] = document.csrfToken
|
|
||||||
return request
|
|
||||||
}).catch(() => {
|
|
||||||
document.csrfToken = null // set mark to not try again
|
|
||||||
return request
|
|
||||||
})
|
|
||||||
|
|
||||||
function fetchToken() {
|
|
||||||
return axios.get('/', { headers: { 'x-csrf-token': 'fetch' } })
|
|
||||||
.then(res => res.headers['x-csrf-token'])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,34 +3,21 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title> Capire Books </title>
|
<title> Capire Books </title>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/primitive-ui/dist/css/main.css">
|
<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/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>
|
<style>
|
||||||
.hovering tr:hover td { color:cyan; background: #123; cursor: pointer; }
|
.hovering tr:hover td { color:cyan; background: #123; cursor: pointer; }
|
||||||
.rating-stars { color:teal }
|
.rating-stars { color:teal }
|
||||||
.succeeded { color:teal }
|
.succeeded { color:teal }
|
||||||
.failed { color:red }
|
.failed { color:red }
|
||||||
.user {text-align: end; color: grey;}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="small-container", style="margin-top: 70px;">
|
<body class="small-container", style="margin-top: 70px;">
|
||||||
<div id='app'>
|
<div id='app'>
|
||||||
|
|
||||||
<form class="user" @submit.prevent="login">
|
<h1> {{ document.title }} </h1>
|
||||||
<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>
|
|
||||||
|
|
||||||
<input type="text" placeholder="Search..." @input="search">
|
<input type="text" placeholder="Search..." @input="search">
|
||||||
|
|
||||||
@@ -47,7 +34,7 @@
|
|||||||
<td>{{ book.author }}</td>
|
<td>{{ book.author }}</td>
|
||||||
<td>{{ book.genre.name }}</td>
|
<td>{{ book.genre.name }}</td>
|
||||||
<td class="rating-stars">
|
<td class="rating-stars">
|
||||||
{{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }} ({{ book.numberOfReviews }})
|
{{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ book.currency && book.currency.symbol }} {{ book.price }}</td>
|
<td>{{ book.currency && book.currency.symbol }} {{ book.price }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -61,7 +48,7 @@
|
|||||||
{{ book.stock }} in stock
|
{{ book.stock }} in stock
|
||||||
</label>
|
</label>
|
||||||
<form @submit.prevent="submitOrder" style="float:right; display:flex; flex-direction:row-reverse">
|
<form @submit.prevent="submitOrder" style="float:right; display:flex; flex-direction:row-reverse">
|
||||||
<input type="number" v-model="order.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">
|
<input type="submit" value="Order:" class="muted-button">
|
||||||
</form>
|
</form>
|
||||||
<h4> {{ book.title }} </h4>
|
<h4> {{ book.title }} </h4>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
ID,name,dateOfBirth,placeOfBirth,dateOfDeath,placeOfDeath
|
ID;name
|
||||||
101,Emily Brontë,1818-07-30,"Thornton, Yorkshire",1848-12-19,"Haworth, Yorkshire"
|
101;Emily Brontë
|
||||||
107,Charlotte Brontë,1818-04-21,"Thornton, Yorkshire",1855-03-31,"Haworth, Yorkshire"
|
107;Charlotte Brontë
|
||||||
150,Edgar Allen Poe,1809-01-19,"Boston, Massachusetts",1849-10-07,"Baltimore, Maryland"
|
150;Edgar Allen Poe
|
||||||
170,Richard Carpenter,1929-08-14,"King’s Lynn, Norfolk",2012-02-26,"Hertfordshire, England"
|
170;Richard Carpenter
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
ID,title,descr,author_ID,stock,price,currency_code,genre_ID
|
ID;title;author_ID;genre_ID
|
||||||
201,Wuthering Heights,"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym ""Ellis Bell"". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.",101,12,11.11,GBP,11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
201;Wuthering Heights;101;11
|
||||||
207,Jane Eyre,"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name ""Currer Bell"", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.",107,11,12.34,GBP,11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
207;Jane Eyre;107;11
|
||||||
251,The Raven,"""The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.",150,333,13.13,USD,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
251;The Raven;150;16
|
||||||
252,Eleonora,"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.",150,555,14,USD,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
252;Eleonora;150;16
|
||||||
271,Catweazle,"Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.",170,22,150,JPY,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
271;Catweazle;170;13
|
||||||
|
|||||||
|
@@ -1,5 +0,0 @@
|
|||||||
ID_texts,ID,locale,title,descr
|
|
||||||
d2a65a27-9f2a-480f-bc38-84ee8ec5c13e,201,de,Sturmhöhe,"Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman der englischen Schriftstellerin Emily Brontë (1818–1848). Der 1847 unter dem Pseudonym Ellis Bell veröffentlichte Roman wurde vom viktorianischen Publikum weitgehend abgelehnt, heute gilt er als ein Klassiker der britischen Romanliteratur des 19. Jahrhunderts."
|
|
||||||
8c42c706-a979-41cf-9ffe-91e6cf1383a0,201,fr,Les Hauts de Hurlevent,"Les Hauts de Hurlevent (titre original : Wuthering Heights), parfois orthographié Les Hauts de Hurle-Vent, est l'unique roman d'Emily Brontë, publié pour la première fois en 1847 sous le pseudonyme d’Ellis Bell. Loin d'être un récit moralisateur, Emily Brontë achève néanmoins le roman dans une atmosphère sereine, suggérant le triomphe de la paix et du Bien sur la vengeance et le Mal."
|
|
||||||
9e1c4c81-dc90-4600-85b1-e9dd4bf12ce0,207,de,Jane Eyre,"Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography), erstmals erschienen im Jahr 1847 unter dem Pseudonym Currer Bell, ist der erste veröffentlichte Roman der britischen Autorin Charlotte Brontë und ein Klassiker der viktorianischen Romanliteratur des 19. Jahrhunderts. Der Roman erzählt in Form einer Ich-Erzählung die Lebensgeschichte von Jane Eyre (ausgesprochen /ˌdʒeɪn ˈɛə/), die nach einer schweren Kindheit eine Stelle als Gouvernante annimmt und sich in ihren Arbeitgeber verliebt, jedoch immer wieder um ihre Freiheit und Selbstbestimmung kämpfen muss. Als klein, dünn, blass, stets schlicht dunkel gekleidet und mit strengem Mittelscheitel beschrieben, gilt die Heldin des Romans Jane Eyre nicht zuletzt aufgrund der Kino- und Fernsehversionen der melodramatischen Romanvorlage als die bekannteste englische Gouvernante der Literaturgeschichte"
|
|
||||||
9be0524b-4cb9-4fc1-9dc2-d65b1c13cf53,252,de,Eleonora,"“Eleonora” ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841 erstveröffentlicht. In ihr geht es um das Paradox der Treue in der Treulosigkeit."
|
|
||||||
|
5
bookshop/db/data/sap.capire.bookshop-Books_texts.csv
Normal file
5
bookshop/db/data/sap.capire.bookshop-Books_texts.csv
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ID;locale;title
|
||||||
|
201;de;Sturmhöhe
|
||||||
|
201;fr;Les Hauts de Hurlevent
|
||||||
|
207;de;Jane Eyre
|
||||||
|
252;de;Eleonora
|
||||||
|
@@ -1,43 +1,21 @@
|
|||||||
ID,parent_ID,name
|
ID;parent_ID;name
|
||||||
10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,,Fiction
|
10;;Fiction
|
||||||
11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Drama
|
11;10;Drama
|
||||||
12aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Poetry
|
12;10;Poetry
|
||||||
13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Fantasy
|
13;10;Fantasy
|
||||||
131aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Fairy Tale
|
14;10;Science Fiction
|
||||||
132aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Epic Fantasy
|
15;10;Romance
|
||||||
133aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,High Fantasy
|
16;10;Mystery
|
||||||
134aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Gothic
|
17;10;Thriller
|
||||||
14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Science Fiction
|
18;10;Dystopia
|
||||||
141aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Utopian and Dystopian
|
19;10;Fairy Tale
|
||||||
1411aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,141aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Utopian
|
20;;Non-Fiction
|
||||||
1412aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,141aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Dystopian
|
21;20;Biography
|
||||||
14121aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1412aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Cyberpunk
|
22;21;Autobiography
|
||||||
141211aa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14121aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Steampunk
|
23;20;Essay
|
||||||
142aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Space Opera
|
24;20;Speech
|
||||||
143aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Time Travel
|
30;;Others
|
||||||
144aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Tech Noir
|
31;30;Other-A
|
||||||
15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Romance
|
32;30;Other-A
|
||||||
151aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Contemporary Romance
|
33;30;Other-A
|
||||||
152aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Historical Romance
|
34;30;Other-A
|
||||||
153aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Romantic Suspense
|
|
||||||
16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Mystery
|
|
||||||
161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Crime
|
|
||||||
1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Thriller
|
|
||||||
16111aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Police Procedural
|
|
||||||
16112aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Legal Thriller
|
|
||||||
16113aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Medical Thriller
|
|
||||||
16114aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Spy Thriller
|
|
||||||
1612aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Detective
|
|
||||||
1613aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Suspense
|
|
||||||
162aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Noir
|
|
||||||
1621aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,162aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Nordic Noir
|
|
||||||
1622aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,162aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Tart Noir
|
|
||||||
163aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Cozy Mystery
|
|
||||||
17aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Adventure
|
|
||||||
18aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Short Story
|
|
||||||
19aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Graphic Novel
|
|
||||||
20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,,Non-Fiction
|
|
||||||
21aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Biography
|
|
||||||
22aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,21aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Autobiography
|
|
||||||
23aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Essay
|
|
||||||
24aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Speech
|
|
||||||
|
|||||||
|
@@ -1,21 +0,0 @@
|
|||||||
const cds = require('@sap/cds')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// NOTE: We use cds.on('served') to delay the UPSERTs after the db init
|
|
||||||
// to run after all INSERTs from .csv files happened.
|
|
||||||
module.exports = cds.on('served', ()=>
|
|
||||||
UPSERT.into ('sap.common.Currencies') .columns (
|
|
||||||
[ 'code', 'symbol', 'name' ]
|
|
||||||
) .rows (
|
|
||||||
[ 'EUR', '€', 'Euro' ],
|
|
||||||
[ 'USD', '$', 'US Dollar' ],
|
|
||||||
[ 'GBP', '£', 'British Pound' ],
|
|
||||||
[ 'ILS', '₪', 'Shekel' ],
|
|
||||||
[ 'JPY', '¥', 'Yen' ],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
@@ -1,38 +1,21 @@
|
|||||||
using { Currency, managed, sap } from '@sap/cds/common';
|
using { Currency, managed, sap } from '@sap/cds/common';
|
||||||
namespace sap.capire.bookshop;
|
namespace sap.capire.bookshop;
|
||||||
|
|
||||||
entity Books : managed {
|
entity Books {
|
||||||
key ID : Integer;
|
key ID : Integer;
|
||||||
title : localized String(111) @mandatory;
|
title : localized String(111);
|
||||||
descr : localized String(1111);
|
author : Association to Authors;
|
||||||
author : Association to Authors @mandatory;
|
|
||||||
genre : Association to Genres;
|
genre : Association to Genres;
|
||||||
stock : Integer;
|
|
||||||
price : Price;
|
|
||||||
currency : Currency;
|
|
||||||
image : LargeBinary @Core.MediaType: 'image/png';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entity Authors : managed {
|
entity Authors {
|
||||||
key ID : Integer;
|
key ID : Integer;
|
||||||
name : String(111) @mandatory;
|
name : String(111);
|
||||||
dateOfBirth : Date;
|
|
||||||
dateOfDeath : Date;
|
|
||||||
placeOfBirth : String;
|
|
||||||
placeOfDeath : String;
|
|
||||||
books : Association to many Books on books.author = $self;
|
books : Association to many Books on books.author = $self;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Hierarchically organized Code List for Genres */
|
|
||||||
entity Genres : sap.common.CodeList {
|
entity Genres : sap.common.CodeList {
|
||||||
key ID : UUID;
|
key ID : Integer;
|
||||||
parent : Association to Genres;
|
parent : Association to Genres;
|
||||||
children : Composition of many Genres on children.parent = $self;
|
children : Composition of many Genres on children.parent = $self;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Price : Decimal(9,2);
|
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
|
||||||
// temporary workaround for reuse in fiori sample and hana deployment
|
|
||||||
annotate Books with @fiori.draft.enabled;
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
namespace sap.capire.bookshop; //> important for reflection
|
namespace sap.capire.bookshop; //> important for reflection
|
||||||
using from './db/schema';
|
using from './db/schema';
|
||||||
using from './srv/cat-service';
|
|
||||||
using from './srv/admin-service';
|
using from './srv/admin-service';
|
||||||
using from './srv/user-service';
|
|
||||||
|
|||||||
@@ -2,22 +2,44 @@
|
|||||||
"name": "@capire/bookshop",
|
"name": "@capire/bookshop",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A simple self-contained bookshop service.",
|
"description": "A simple self-contained bookshop service.",
|
||||||
"files": [
|
|
||||||
"app",
|
|
||||||
"srv",
|
|
||||||
"db",
|
|
||||||
"index.cds"
|
|
||||||
],
|
|
||||||
"devDependencies": {
|
|
||||||
"@cap-js/sqlite": ">=1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": ">=7",
|
"@sap/cds": "^5.0.4",
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1",
|
||||||
|
"passport": "0.4.1",
|
||||||
|
"@sap/hana-client": "^2.7.21"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"genres": "cds serve test/genres.cds",
|
|
||||||
"start": "cds-serve",
|
|
||||||
"watch": "cds watch"
|
"watch": "cds watch"
|
||||||
|
},
|
||||||
|
"cds": {
|
||||||
|
"requires": {
|
||||||
|
"db": {
|
||||||
|
"kind": "sql",
|
||||||
|
"xxx-model": "*",
|
||||||
|
"xxx-credentials": {
|
||||||
|
"database": "localSqlite.db"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"[production]": {
|
||||||
|
"db": {
|
||||||
|
"kind": "hana",
|
||||||
|
"model": [
|
||||||
|
"db",
|
||||||
|
"srv"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"assert_integrity": false
|
||||||
|
},
|
||||||
|
"hana": {
|
||||||
|
"deploy-format": "hdbtable"
|
||||||
|
},
|
||||||
|
"cdsc": {
|
||||||
|
"beta": {
|
||||||
|
"foreignKeyConstraints": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,126 @@
|
|||||||
# Bookshop Getting Started Sample
|
# Foreign key constraints on DB
|
||||||
|
|
||||||
This stand-alone sample introduces the essential tasks in the development of CAP-based services as also covered in the [Getting Started guide in capire](https://cap.cloud.sap/docs/get-started/in-a-nutshell).
|
As model we use a simplified version of the bookshop scenario, we work with nodejs runtime.
|
||||||
|
|
||||||
## Hypothetical Use Cases
|
|
||||||
|
|
||||||
1. Build a service that allows to browse _Books_ and _Authors_.
|
## Preparation
|
||||||
2. Books have assigned _Genres_, which are organized hierarchically.
|
|
||||||
3. All users may browse books without login.
|
|
||||||
4. All entries are maintained by Administrators.
|
|
||||||
5. End users may order books (the actual order mgmt being out of scope).
|
|
||||||
|
|
||||||
## Running the Sample
|
As we want to see the effects of the database constaints, we first need to switch off the integrity checks done by the node runtime. In the `package.json`:
|
||||||
|
```json
|
||||||
```sh
|
"cds": {
|
||||||
npm run watch
|
...,
|
||||||
|
"features": {
|
||||||
|
"assert_integrity": false
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Content & Best Practices
|
Then switch on constraint generation. In the `package.json`:
|
||||||
|
```json
|
||||||
|
"cds": {
|
||||||
|
...,
|
||||||
|
"cdsc": {
|
||||||
|
"beta": {
|
||||||
|
"foreignKeyConstraints": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
| Links to capire | Sample files / folders |
|
## SQLite
|
||||||
| --------------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
|
||||||
| [Project Setup & Layouts](https://cap.cloud.sap/docs/get-started/jumpstart#project-structure) | [`./`](./) |
|
In SQLite, FK constraints can only be specified during the creation of a table. They cannot be altered for an existing table. So here we at least don't have issues with existing data.
|
||||||
| [Domain Modeling with CDS](https://cap.cloud.sap/docs/guides/domain-modeling) | [`./db/schema.cds`](./db/schema.cds) |
|
|
||||||
| [Defining Services](https://cap.cloud.sap/docs/guides/providing-services#modeling-services) | [`./srv/*.cds`](./srv) |
|
For SQLite, constraint checks need to be enabled by application at runtime for each db connection with the command
|
||||||
| [Single-purposed Services](https://cap.cloud.sap/docs/guides/providing-services#single-purposed-services) | [`./srv/*.cds`](./srv) |
|
```sql
|
||||||
| [Providing & Consuming Providers](https://cap.cloud.sap/docs/guides/providing-services) | http://localhost:4004 |
|
PRAGMA foreign_keys = ON;
|
||||||
| [Using Databases](https://cap.cloud.sap/docs/guides/databases) | [`./db/data/*.csv`](./db/data) |
|
```
|
||||||
| [Adding Custom Logic](https://cap.cloud.sap/docs/guides/providing-services#adding-custom-logic) | [`./srv/*.js`](./srv) |
|
We do this via a custom handler in `admin-service.js`.
|
||||||
| Adding Tests | [`./test`](./test) |
|
|
||||||
| [Sharing for Reuse](https://cap.cloud.sap/docs/guides/extensibility/composition) | [`./index.cds`](./index.cds) |
|
Now run the requests in `requests.http` (use `http://localhost:4004` as server URL). They behave as expected.
|
||||||
|
|
||||||
|
But the error messages are not providing any details. They say "constraint violation", but they don't say which table/constraint is violated:
|
||||||
|
```
|
||||||
|
HTTP/1.1 500 Internal Server Error
|
||||||
|
...
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"code": "SQLITE_CONSTRAINT",
|
||||||
|
"message": "SQLITE_CONSTRAINT: FOREIGN KEY constraint failed in: \nCOMMIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The runtime constraint checks do provide the name of the restricting association (but not the name of the entity where the association lives).
|
||||||
|
|
||||||
|
## HANA
|
||||||
|
|
||||||
|
Preparation:
|
||||||
|
* logon to cf account (e.g. trial)
|
||||||
|
* create HANA service instance: `cf create-service hanatrial hdi-shared bookshop-db`
|
||||||
|
|
||||||
|
First make the deplyoment without constraints (switch off constraint generation in `package.json`).
|
||||||
|
|
||||||
|
* Run `cds build --production`. No constraints are generated.
|
||||||
|
* Deploy db model: `cf push -f gen/db`
|
||||||
|
* Deploy nodejs app: `cf push -f gen/srv --random-route`. Take note of the app URL.
|
||||||
|
|
||||||
|
Run the requests in `request.http` (use the URL from the previous step as server URL, prepend with `https://`).
|
||||||
|
Make sure to introduce some data that violates the constraints. No errors are reported - as expected.
|
||||||
|
|
||||||
|
Now switch on constraint generation in `package.json`.
|
||||||
|
|
||||||
|
* Run `cds build --production`. The constraints are placed in separate `hdbconstraint` files in `gen\db\src\gen`. They are generated with `VALIDATED ON`.
|
||||||
|
* Re-deploy db model: `cf push -f gen/db`. It fails, as the existing data violates the constraints.
|
||||||
|
* Check logs: `cf logs bookshop-db-deployer --recent`.
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: com.sap.hana.di.constraint: Could not create the database object [8250005]
|
||||||
|
at "src/gen/sap.capire.bookshop.Books_author.hdbconstraint" (1:1)
|
||||||
|
Error: com.sap.hana.di.constraint: Database error 7: : feature not supported: referential constraint violated by existing value [8201003]
|
||||||
|
at "src/gen/sap.capire.bookshop.Books_author.hdbconstraint" (1:1)
|
||||||
|
```
|
||||||
|
You can see which constraint is violated, but there is no info about the offensive entries.
|
||||||
|
|
||||||
|
Now use the requests from `request.http` to fix the entries that have been broken above.
|
||||||
|
|
||||||
|
* Re-deploy db model: `cf push -f gen/db`. It should succeed.
|
||||||
|
|
||||||
|
Again run the requests from `request.http`. This time all changes that would lead to a
|
||||||
|
constraint violation fail.
|
||||||
|
|
||||||
|
Inserting a book with non-existing author ID or changing a book's author ID to an invalid value yields
|
||||||
|
the message
|
||||||
|
```
|
||||||
|
HTTP/1.1 500 Internal Server Error
|
||||||
|
...
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"code": "500",
|
||||||
|
"message": "Internal Server Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
When trying to delete a genre that is still referenced in a book we get
|
||||||
|
```
|
||||||
|
HTTP/1.1 462 status code 462
|
||||||
|
...
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"code": "462",
|
||||||
|
"message": "failed on update or delete by foreign key constraint violation: TrexColumnUpdate failed on table '21F87859727E470E94917B690908A09D:SAP_CAPIRE_BOOKSHOP_GENRES' with error: DELETE on 21F87859727E470E94917B690908A09D:SAP_CAPIRE_BOOKSHOP_GENRES(ID) failed, because 1 rows with corresponding foreign keys still exist in 21F87859727E470E94917B690908A09D:SAP_CAPIRE_BOOKSHOP_BOOKS(GENRE_ID), rc=1536"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: when using `cdsc` to generate the constraints
|
||||||
|
```
|
||||||
|
cdsc Q --beta foreignKeyConstraints -d hana -s hdi -o dbstuff db\schema.cds srv\admin-service.cds
|
||||||
|
```
|
||||||
|
there will be constraints for e.g. `sap_common_Currencies_texts`. This cannot be deplyoed, as the corresponding table is removed from `cds` via the tree-shaking mechanism. These constraints need to be removed manually before deployment.
|
||||||
|
|
||||||
|
## Todo
|
||||||
|
|
||||||
|
* Provide a way to control parameters `VALIDATED` and `ENFORCED` of the constraints (only interesting for HANA, as in SQLite changing constraints after table generation is not possible).
|
||||||
|
* Provide some tool to identify existing entries that violate constraints. We can generate a list of `SELECT` statements that find these entries, but how do we run it and show the results to the user?
|
||||||
|
* Can runtimes improve the error messages?
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
using { AdminService } from './admin-service';
|
|
||||||
annotate AdminService with @requires:'admin';
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using { sap.capire.bookshop as my } from '../db/schema';
|
using { sap.capire.bookshop as my } from '../db/schema';
|
||||||
service AdminService @(path:'/admin') {
|
service AdminService {
|
||||||
entity Authors as projection on my.Authors;
|
|
||||||
entity Books as projection on my.Books;
|
entity Books as projection on my.Books;
|
||||||
|
entity Authors as projection on my.Authors;
|
||||||
entity Genres as projection on my.Genres;
|
entity Genres as projection on my.Genres;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
|
// sqlite: FK constraint checks need to be enabled by application at runtime
|
||||||
|
// for each db connection
|
||||||
const cds = require('@sap/cds')
|
const cds = require('@sap/cds')
|
||||||
|
module.exports = cds.service.impl (async function(){
|
||||||
|
|
||||||
module.exports = class AdminService extends cds.ApplicationService { init(){
|
const db = await cds.connect.to('db')
|
||||||
this.before (['NEW','CREATE'],'Authors', genid)
|
|
||||||
this.before (['NEW','CREATE'],'Books', genid)
|
|
||||||
return super.init()
|
|
||||||
}}
|
|
||||||
|
|
||||||
/** Generate primary keys for target entity in request */
|
if (db.kind === 'sqlite')
|
||||||
async function genid (req) {
|
{
|
||||||
if (req.data.ID) return
|
db.before('BEGIN', function (req) {
|
||||||
const {id} = await SELECT.one.from(req.target).columns('max(ID) as id')
|
//console.log('--- PRAGMA foreign_keys = ON ---')
|
||||||
req.data.ID = id + 4 // Note: that is not safe! ok for this sample only.
|
return this.dbc.run('PRAGMA foreign_keys = ON;')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
using { sap.capire.bookshop as my } from '../db/schema';
|
|
||||||
service CatalogService @(path:'/browse') {
|
|
||||||
|
|
||||||
/** For displaying lists of Books */
|
|
||||||
@readonly entity ListOfBooks as projection on Books
|
|
||||||
excluding { descr };
|
|
||||||
|
|
||||||
/** For display in details pages */
|
|
||||||
@readonly entity Books as projection on my.Books { *,
|
|
||||||
author.name as author
|
|
||||||
} 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 };
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
const cds = require('@sap/cds')
|
|
||||||
|
|
||||||
class CatalogService extends cds.ApplicationService { init() {
|
|
||||||
|
|
||||||
const { Books } = cds.entities('sap.capire.bookshop')
|
|
||||||
const { ListOfBooks } = this.entities
|
|
||||||
|
|
||||||
// Add some discount for overstocked books
|
|
||||||
this.after('each', ListOfBooks, book => {
|
|
||||||
if (book.stock > 111) book.title += ` -- 11% discount!`
|
|
||||||
})
|
|
||||||
|
|
||||||
// Reduce stock of ordered books if available stock suffices
|
|
||||||
this.on('submitOrder', async req => {
|
|
||||||
let { book:id, quantity } = req.data
|
|
||||||
let book = await SELECT.from (Books, id, b => b.stock)
|
|
||||||
|
|
||||||
// Validate input data
|
|
||||||
if (!book) return req.error (404, `Book #${id} doesn't exist`)
|
|
||||||
if (quantity < 1) return req.error (400, `quantity has to be 1 or more`)
|
|
||||||
if (quantity > book.stock) return req.error (409, `${quantity} exceeds stock for book #${id}`)
|
|
||||||
|
|
||||||
// Reduce stock in database and return updated stock value
|
|
||||||
await UPDATE (Books, id) .with ({ stock: book.stock -= quantity })
|
|
||||||
return book
|
|
||||||
})
|
|
||||||
|
|
||||||
// Emit event when an order has been submitted
|
|
||||||
this.after('submitOrder', async (_,req) => {
|
|
||||||
let { book, quantity } = req.data
|
|
||||||
await this.emit('OrderedBook', { book, quantity, buyer: req.user.id })
|
|
||||||
})
|
|
||||||
|
|
||||||
// Delegate requests to the underlying generic service
|
|
||||||
return super.init()
|
|
||||||
}}
|
|
||||||
|
|
||||||
module.exports = CatalogService
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
/**
|
|
||||||
* Exposes user information
|
|
||||||
*/
|
|
||||||
service UserService @(path: '/user') {
|
|
||||||
/**
|
|
||||||
* The current user
|
|
||||||
*/
|
|
||||||
@odata.singleton entity me @cds.persistence.skip {
|
|
||||||
id : String; // user id
|
|
||||||
locale : String;
|
|
||||||
tenant : String;
|
|
||||||
}
|
|
||||||
|
|
||||||
action login() returns me;
|
|
||||||
}
|
|
||||||
@@ -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')
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
using { sap.capire.bookshop as my } from '../db/schema';
|
|
||||||
service TestService {
|
|
||||||
entity Genres as projection on my.Genres;
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#################################################
|
|
||||||
#
|
|
||||||
# Genres
|
|
||||||
#
|
|
||||||
|
|
||||||
GET http://localhost:4004/odata/v4/test/Genres?
|
|
||||||
###
|
|
||||||
|
|
||||||
GET http://localhost:4004/odata/v4/test/Genres?
|
|
||||||
&$filter=parent_ID eq null&$select=name
|
|
||||||
&$expand=children($select=name)
|
|
||||||
###
|
|
||||||
|
|
||||||
POST http://localhost:4004/odata/v4/test/Genres?
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{ "ID":100, "name":"Some Sample Genres...", "children":[
|
|
||||||
{ "ID":101, "name":"Cat", "children":[
|
|
||||||
{ "ID":102, "name":"Kitty", "children":[
|
|
||||||
{ "ID":103, "name":"Kitty Cat", "children":[
|
|
||||||
{ "ID":104, "name":"Aristocat" } ]},
|
|
||||||
{ "ID":105, "name":"Kitty Bat" } ]},
|
|
||||||
{ "ID":106, "name":"Catwoman", "children":[
|
|
||||||
{ "ID":107, "name":"Catalina" } ]} ]},
|
|
||||||
{ "ID":108, "name":"Catweazle" }
|
|
||||||
]}
|
|
||||||
###
|
|
||||||
|
|
||||||
GET http://localhost:4004/odata/v4/test/Genres(100)?
|
|
||||||
# &$expand=children
|
|
||||||
# &$expand=children($expand=children($expand=children($expand=children)))
|
|
||||||
###
|
|
||||||
|
|
||||||
DELETE http://localhost:4004/odata/v4/test/Genres(103)
|
|
||||||
###
|
|
||||||
|
|
||||||
DELETE http://localhost:4004/odata/v4/test/Genres(100)
|
|
||||||
###
|
|
||||||
@@ -1,95 +1,68 @@
|
|||||||
|
# running locally
|
||||||
@server = http://localhost:4004
|
@server = http://localhost:4004
|
||||||
@me = Authorization: Basic {{$processEnv USER}}:
|
|
||||||
|
# running on cf
|
||||||
|
#@server = https://bookshop-srv-....hana.ondemand.com
|
||||||
|
|
||||||
|
### get all books
|
||||||
|
GET {{server}}/admin/Books
|
||||||
|
|
||||||
|
### get all authors
|
||||||
|
GET {{server}}/admin/Authors
|
||||||
|
|
||||||
|
### get all genres
|
||||||
|
GET {{server}}/admin/Genres
|
||||||
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
### create book with wrong author ID -> should fail
|
||||||
# Get service info
|
POST {{server}}/admin/Books
|
||||||
GET {{server}}/browse
|
Content-Type: application/json
|
||||||
{{me}}
|
|
||||||
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
|
||||||
# Get $metadata document
|
|
||||||
GET {{server}}/browse/$metadata
|
|
||||||
{{me}}
|
|
||||||
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
|
||||||
# Browse Books as any user
|
|
||||||
GET {{server}}/browse/ListOfBooks?
|
|
||||||
# &$select=title,stock
|
|
||||||
&$expand=genre
|
|
||||||
# &sap-language=de
|
|
||||||
{{me}}
|
|
||||||
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
|
||||||
# Fetch Authors as admin
|
|
||||||
GET {{server}}/admin/Authors?
|
|
||||||
# &$select=name,dateOfBirth,placeOfBirth
|
|
||||||
# &$expand=books($select=title;$expand=currency)
|
|
||||||
# &$filter=ID eq 101
|
|
||||||
# &sap-language=de
|
|
||||||
Authorization: Basic alice:
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
|
||||||
# Create Author
|
|
||||||
POST {{server}}/admin/Authors
|
|
||||||
Content-Type: application/json;IEEE754Compatible=true
|
|
||||||
Authorization: Basic alice:
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"ID": 112,
|
"ID": 701,
|
||||||
"name": "Shakespeeeeere",
|
"title": "XXX",
|
||||||
"age": 22
|
"author": { "ID": 9999 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
### create book with correct author ID -> should work
|
||||||
### ------------------------------------------------------------------------
|
|
||||||
# Create book
|
|
||||||
POST {{server}}/admin/Books
|
POST {{server}}/admin/Books
|
||||||
Content-Type: application/json;IEEE754Compatible=true
|
Content-Type: application/json;IEEE754Compatible=true
|
||||||
Authorization: Basic alice:
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"ID": 2,
|
"ID": 702,
|
||||||
"title": "Poems : Pocket Poets",
|
"title": "Poems : Pocket Poets",
|
||||||
"descr": "The Everyman's Library Pocket Poets hardcover series is popular for its compact size and reasonable price which does not compromise content. Poems: Bronte contains poems that demonstrate a sensibility elemental in its force with an imaginative discipline and flexibility of the highest order. Also included are an Editor's Note and an index of first lines.",
|
"author": { "ID": 101 }
|
||||||
"author": { "ID": 101 },
|
|
||||||
"genre": { "ID": 12 },
|
|
||||||
"stock": 5,
|
|
||||||
"price": "12.05",
|
|
||||||
"currency": { "code": "USD" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
### delete book
|
||||||
|
DELETE {{server}}/admin/Books(ID=701)
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
### change book to wrong author ID -> should fail
|
||||||
# Put image to books
|
|
||||||
PUT {{server}}/admin/Books(2)/image
|
|
||||||
Content-Type: image/png
|
|
||||||
Authorization: Basic alice:
|
|
||||||
|
|
||||||
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANwAAADcCAYAAAAbWs+BAAAGwElEQVR4Ae3cwZFbNxBFUY5rkrDTmKAUk5QT03Aa44U22KC7NHptw+DRikVAXf8fzC3u8Hj4R4AAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgZzAW26USQT+e4HPx+Mz+RRvj0e0kT+SD2cWAQK1gOBqH6sEogKCi3IaRqAWEFztY5VAVEBwUU7DCNQCgqt9rBKICgguymkYgVpAcLWPVQJRAcFFOQ0jUAsIrvaxSiAqILgop2EEagHB1T5WCUQFBBflNIxALSC42scqgaiA4KKchhGoBQRX+1glEBUQXJTTMAK1gOBqH6sEogKCi3IaRqAWeK+Xb1z9iN558fHxcSPS9p2ezx/ROz4e4TtIHt+3j/61hW9f+2+7/+UXbifjewIDAoIbQDWSwE5AcDsZ3xMYEBDcAKqRBHYCgtvJ+J7AgIDgBlCNJLATENxOxvcEBgQEN4BqJIGdgOB2Mr4nMCAguAFUIwnsBAS3k/E9gQEBwQ2gGklgJyC4nYzvCQwICG4A1UgCOwHB7WR8T2BAQHADqEYS2AkIbifjewIDAoIbQDWSwE5AcDsZ3xMYEEjfTzHwiK91B8npd6Q8n8/oGQ/ckRJ9vvQwv3BpUfMIFAKCK3AsEUgLCC4tah6BQkBwBY4lAmkBwaVFzSNQCAiuwLFEIC0guLSoeQQKAcEVOJYIpAUElxY1j0AhILgCxxKBtIDg0qLmESgEBFfgWCKQFhBcWtQ8AoWA4AocSwTSAoJLi5pHoBAQXIFjiUBaQHBpUfMIFAKCK3AsEUgLCC4tah6BQmDgTpPsHSTFs39p6fQ7Q770UsV/Ov19X+2OFL9wxR+rJQJpAcGlRc0jUAgIrsCxRCAtILi0qHkECgHBFTiWCKQFBJcWNY9AISC4AscSgbSA4NKi5hEoBARX4FgikBYQXFrUPAKFgOAKHEsE0gKCS4uaR6AQEFyBY4lAWkBwaVHzCBQCgitwLBFICwguLWoegUJAcAWOJQJpAcGlRc0jUAgIrsCxRCAt8J4eePq89B0ar3ZnyOnve/rfn1+400/I810lILirjtPLnC4guNNPyPNdJSC4q47Ty5wuILjTT8jzXSUguKuO08ucLiC400/I810lILirjtPLnC4guNNPyPNdJSC4q47Ty5wuILjTT8jzXSUguKuO08ucLiC400/I810lILirjtPLnC4guNNPyPNdJSC4q47Ty5wuILjTT8jzXSUguKuO08ucLiC400/I810l8JZ/m78+szP/zI47fJo7Q37vgJ7PHwN/07/3TOv/9gu3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhAcMPAxhNYBQS3avhMYFhg4P6H9J0maYHXuiMlrXf+vOfA33Turf3C5SxNItAKCK4lsoFATkBwOUuTCLQCgmuJbCCQExBcztIkAq2A4FoiGwjkBASXszSJQCsguJbIBgI5AcHlLE0i0AoIriWygUBOQHA5S5MItAKCa4lsIJATEFzO0iQCrYDgWiIbCOQEBJezNIlAKyC4lsgGAjkBweUsTSLQCgiuJbKBQE5AcDlLkwi0Akff//Dz6U+/I6U1/sUNr3bnytl3kPzi4bXb/cK1RDYQyAkILmdpEoFWQHAtkQ0EcgKCy1maRKAVEFxLZAOBnIDgcpYmEWgFBNcS2UAgJyC4nKVJBFoBwbVENhDICQguZ2kSgVZAcC2RDQRyAoLLWZpEoBUQXEtkA4GcgOByliYRaAUE1xLZQCAnILicpUkEWgHBtUQ2EMgJCC5naRKBVkBwLZENBHIC/4M7TXIv+3PS22d24qvdQfL3C/7N5P5i/MLlLE0i0AoIriWygUBOQHA5S5MItAKCa4lsIJATEFzO0iQCrYDgWiIbCOQEBJezNIlAKyC4lsgGAjkBweUsTSLQCgiuJbKBQE5AcDlLkwi0AoJriWwgkBMQXM7SJAKtgOBaIhsI5AQEl7M0iUArILiWyAYCOQHB5SxNItAKCK4lsoFATkBwOUuTCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIDAvyrwDySEJ2VQgUSoAAAAAElFTkSuQmCC
|
PATCH {{server}}/admin/Books(ID=207)
|
||||||
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
|
||||||
# Reading image from from the server directly
|
|
||||||
GET {{server}}/browse/Books(2)/image
|
|
||||||
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
|
||||||
# Submit Order as authenticated user
|
|
||||||
# (send that three times to get out-of-stock message)
|
|
||||||
POST {{server}}/browse/submitOrder
|
|
||||||
Content-Type:application/json
|
Content-Type:application/json
|
||||||
{{me}}
|
|
||||||
|
|
||||||
{ "book":201, "quantity":5 }
|
{"author_ID": 9999}
|
||||||
|
|
||||||
|
### change book to correct author ID -> should work
|
||||||
|
|
||||||
|
PATCH {{server}}/admin/Books(ID=207)
|
||||||
|
Content-Type:application/json
|
||||||
|
|
||||||
|
{"author_ID": 170}
|
||||||
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
|
||||||
# Browse Genres
|
### delete "leaf" genre that is used in a book -> should not work
|
||||||
GET {{server}}/browse/Genres?
|
DELETE {{server}}/admin/Genres(ID=13)
|
||||||
# &$filter=parent_ID eq null&$select=name
|
|
||||||
# &$expand=children($select=name)
|
### delete "leaf" genre that is not used in a book -> should work
|
||||||
{{me}}
|
DELETE {{server}}/admin/Genres(ID=14)
|
||||||
|
|
||||||
|
|
||||||
|
### delete "header" genre where leaves are used in a book -> should not work
|
||||||
|
DELETE {{server}}/admin/Genres(ID=10)
|
||||||
|
|
||||||
|
|
||||||
|
### delete "header" genre where leaves are not used in a book -> should work and delete all leaves
|
||||||
|
DELETE {{server}}/admin/Genres(ID=30)
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
Books = Bücher
|
|
||||||
Book = Buch
|
|
||||||
Title = Titel
|
|
||||||
Description = Beschreibung
|
|
||||||
Stock = Bestand
|
|
||||||
Image = Bild
|
|
||||||
Price = Preis
|
|
||||||
Currency = Währung
|
|
||||||
|
|
||||||
Authors = Autoren
|
|
||||||
Author = Autor
|
|
||||||
AuthorID = ID des Autors
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
Books = Livres
|
|
||||||
Book = Livre
|
|
||||||
Title = Titre
|
|
||||||
Description = Description
|
|
||||||
Stock = Action
|
|
||||||
Image = Image
|
|
||||||
Price = Prix
|
|
||||||
Currency = Devise
|
|
||||||
|
|
||||||
Authors = Auteurs
|
|
||||||
Author = Auteur
|
|
||||||
AuthorID = ID de l''auteur
|
|
||||||
Name = Nom
|
|
||||||
DateOfBirth = Date de naissance
|
|
||||||
DateOfDeath = Date de décès
|
|
||||||
PlaceOfBirth = Lieu de naissance
|
|
||||||
PlaceOfDeath = Lieu de décès
|
|
||||||
|
|
||||||
Genres = Genre
|
|
||||||
Genre = Genre
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
400 = Ungültige Anfrage
|
|
||||||
401 = Nicht autorisiert
|
|
||||||
403 = Verboten
|
|
||||||
404 = Nicht gefunden
|
|
||||||
405 = Methode nicht zulässig
|
|
||||||
406 = Nicht akzeptabel
|
|
||||||
407 = Proxy-Authentifizierung erforderlich
|
|
||||||
408 = Anfrage-Timeout
|
|
||||||
409 = Konflikt
|
|
||||||
410 = Weg
|
|
||||||
411 = Erforderliche Länge
|
|
||||||
412 = Vorbedingung fehlgeschlagen
|
|
||||||
413 = Nutzlast zu groß
|
|
||||||
414 = URI zu lang
|
|
||||||
415 = Nicht unterstützter Medientyp
|
|
||||||
416 = Bereich nicht erfüllbar
|
|
||||||
417 = Erwartung fehlgeschlagen
|
|
||||||
422 = Nicht verarbeitbarer Inhalt
|
|
||||||
424 = Fehlgeschlagene Abhängigkeit
|
|
||||||
428 = Vorbedingung erforderlich
|
|
||||||
429 = Zu viele Anfragen
|
|
||||||
431 = Anfrage-Headerfelder zu groß
|
|
||||||
451 = Aus rechtlichen Gründen nicht verfügbar
|
|
||||||
500 = Interner Serverfehler
|
|
||||||
501 = Der Server unterstützt die zur Erfüllung der Anfrage erforderliche Funktionalität nicht
|
|
||||||
502 = Ungültiges Gateway
|
|
||||||
503 = Dienst nicht verfügbar
|
|
||||||
504 = Gateway-Timeout
|
|
||||||
|
|
||||||
ASSERT_RANGE = Wert {0} liegt nicht im angegebenen Bereich [{1}, {2}]
|
|
||||||
ASSERT_FORMAT = Wert "{0}" liegt nicht im angegebenen Format "{1}"
|
|
||||||
ASSERT_ARRAY = Wert muss ein Array sein
|
|
||||||
ASSERT_ENUM = Wert {0} ist gemäß Enumerationsdeklaration {{1}} ungültig
|
|
||||||
ASSERT_NOT_NULL = Wert ist erforderlich
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
400 = Bad Request
|
|
||||||
401 = Unauthorized
|
|
||||||
403 = Forbidden
|
|
||||||
404 = Not Found
|
|
||||||
405 = Method Not Allowed
|
|
||||||
406 = Not Acceptable
|
|
||||||
407 = Proxy Authentication Required
|
|
||||||
408 = Request Timeout
|
|
||||||
409 = Conflict
|
|
||||||
410 = Gone
|
|
||||||
411 = Length Required
|
|
||||||
412 = Precondition Failed
|
|
||||||
413 = Payload Too Large
|
|
||||||
414 = URI Too Long
|
|
||||||
415 = Unsupported Media Type
|
|
||||||
416 = Range Not Satisfiable
|
|
||||||
417 = Expectation Failed
|
|
||||||
422 = Unprocessable Content
|
|
||||||
424 = Failed Dependency
|
|
||||||
428 = Precondition Required
|
|
||||||
429 = Too Many Requests
|
|
||||||
431 = Request Header Fields Too Large
|
|
||||||
451 = Unavailable For Legal Reasons
|
|
||||||
500 = Internal Server Error
|
|
||||||
501 = The server does not support the functionality required to fulfill the request
|
|
||||||
502 = Bad Gateway
|
|
||||||
503 = Service Unavailable
|
|
||||||
504 = Gateway Timeout
|
|
||||||
|
|
||||||
ASSERT_RANGE = Value {0} is not in specified range [{1}, {2}]
|
|
||||||
ASSERT_FORMAT = Value "{0}" is not in specified format "{1}"
|
|
||||||
ASSERT_ARRAY = Value must be an array
|
|
||||||
ASSERT_ENUM = Value {0} is invalid according to enum declaration {{1}}
|
|
||||||
ASSERT_NOT_NULL = Value is required
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
400 = Requête incorrecte
|
|
||||||
401 = Non autorisée
|
|
||||||
403 = Interdite
|
|
||||||
404 = Introuvable
|
|
||||||
405 = Méthode non autorisée
|
|
||||||
406 = Non acceptable
|
|
||||||
407 = Authentification proxy requise
|
|
||||||
408 = Délai d''expiration de la requête
|
|
||||||
409 = Conflit
|
|
||||||
410 = Disparu
|
|
||||||
411 = Longueur requise
|
|
||||||
412 = Échec de la condition préalable
|
|
||||||
413 = Charge utile trop importante
|
|
||||||
414 = URI trop longue
|
|
||||||
415 = Type de média non pris en charge
|
|
||||||
416 = Plage non satisfaisante
|
|
||||||
417 = Échec de l''attente
|
|
||||||
422 = Contenu non traitable
|
|
||||||
424 = Dépendance échouée
|
|
||||||
428 = Condition préalable requise
|
|
||||||
429 = Trop de requêtes
|
|
||||||
431 = Champs d''en-tête de requête trop importants
|
|
||||||
451 = Indisponible pour des raisons juridiques
|
|
||||||
500 = Erreur interne du serveur
|
|
||||||
501 = Le serveur ne prend pas en charge la fonctionnalité requise pour répondre à la requête
|
|
||||||
502 = Passerelle incorrecte
|
|
||||||
503 = Service indisponible
|
|
||||||
504 = Délai d''attente de la passerelle
|
|
||||||
|
|
||||||
ASSERT_RANGE = La valeur {0} n''est pas dans la plage spécifiée [{1}, {2}]
|
|
||||||
ASSERT_FORMAT = La valeur "{0}" n''est pas au format spécifié "{1}"
|
|
||||||
ASSERT_ARRAY = La valeur doit être un tableau
|
|
||||||
ASSERT_ENUM = La valeur {0} n''est pas valide selon la déclaration d''énumération {{1}}
|
|
||||||
ASSERT_NOT_NULL = La valeur est obligatoire
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
require('./srv/server')
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
namespace sap.capire.bookshop; //> important for reflection
|
|
||||||
using from './srv/mashup';
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@capire/bookstore",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@capire/bookshop": "*",
|
|
||||||
"@capire/reviews": "*",
|
|
||||||
"@capire/orders": "*",
|
|
||||||
"@capire/common": "*",
|
|
||||||
"@capire/data-viewer": "*",
|
|
||||||
"@sap-cloud-sdk/http-client": "^4",
|
|
||||||
"@sap-cloud-sdk/resilience": "^4",
|
|
||||||
"@sap/cds": ">=5",
|
|
||||||
"express": "^4.17.1",
|
|
||||||
"@cap-js/hana": ">=1"
|
|
||||||
},
|
|
||||||
"cds": {
|
|
||||||
"requires": {
|
|
||||||
"ReviewsService": {
|
|
||||||
"kind": "odata",
|
|
||||||
"model": "@capire/reviews"
|
|
||||||
},
|
|
||||||
"OrdersService": {
|
|
||||||
"kind": "odata",
|
|
||||||
"model": "@capire/orders"
|
|
||||||
},
|
|
||||||
"messaging": true,
|
|
||||||
"db": true
|
|
||||||
},
|
|
||||||
"log": { "service": true }
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "cds-serve"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Enhancing bookshop with Reviews and Orders provided through
|
|
||||||
// respective reuse packages and services
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Extend Books with access to Reviews and average ratings
|
|
||||||
//
|
|
||||||
using { sap.capire.bookshop.Books } from '@capire/bookshop';
|
|
||||||
using { ReviewsService.Reviews } from '@capire/reviews';
|
|
||||||
extend Books with {
|
|
||||||
reviews : Composition of many Reviews on reviews.subject = $self.ID;
|
|
||||||
rating : type of Reviews:rating; // average rating
|
|
||||||
numberOfReviews : Integer @title : '{i18n>NumberOfReviews}';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Extend Orders with Books as Products
|
|
||||||
//
|
|
||||||
using { sap.capire.orders.Orders } from '@capire/orders';
|
|
||||||
extend Orders:Items with {
|
|
||||||
book : Association to Books on product.ID = book.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure models from all imported packages are loaded
|
|
||||||
using from '@capire/orders/app/fiori';
|
|
||||||
using from '@capire/data-viewer';
|
|
||||||
using from '@capire/common';
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
const cds = require ('@sap/cds')
|
|
||||||
|
|
||||||
|
|
||||||
// Add routes to UIs from imported packages
|
|
||||||
if (!cds.env.production) 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')
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// Mashing up bookshop services with required services...
|
|
||||||
cds.once ('served', async ()=>{
|
|
||||||
|
|
||||||
const CatalogService = await cds.connect.to ('CatalogService')
|
|
||||||
const ReviewsService = await cds.connect.to ('ReviewsService')
|
|
||||||
const OrdersService = await cds.connect.to ('OrdersService')
|
|
||||||
const db = await cds.connect.to ('db')
|
|
||||||
|
|
||||||
// reflect entity definitions used below...
|
|
||||||
const { Books } = db.entities ('sap.capire.bookshop')
|
|
||||||
|
|
||||||
//
|
|
||||||
// Delegate requests to read reviews to the ReviewsService
|
|
||||||
// Note: prepend is neccessary to intercept generic default handler
|
|
||||||
//
|
|
||||||
CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => {
|
|
||||||
console.debug ('> delegating request to ReviewsService') // eslint-disable-line no-console
|
|
||||||
const [id] = req.params, { columns, limit } = req.query.SELECT
|
|
||||||
return ReviewsService.read ('Reviews',columns).limit(limit).where({subject:String(id)})
|
|
||||||
}))
|
|
||||||
|
|
||||||
//
|
|
||||||
// Create an order with the OrdersService when CatalogService signals a new order
|
|
||||||
//
|
|
||||||
CatalogService.on ('OrderedBook', async (msg) => {
|
|
||||||
const { book, quantity, buyer } = msg.data
|
|
||||||
const { title, price } = await db.read (Books, book, b => { b.title, b.price })
|
|
||||||
return OrdersService.create ('OrdersNoDraft').entries({
|
|
||||||
OrderNo: 'Order at '+ (new Date).toLocaleString(),
|
|
||||||
Items: [{ product:{ID:`${book}`}, title, price, quantity }],
|
|
||||||
buyer, createdBy: buyer
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
//
|
|
||||||
// Update Books' average ratings when ReviewsService signals updated reviews
|
|
||||||
//
|
|
||||||
ReviewsService.on ('reviewed', (msg) => {
|
|
||||||
console.debug ('> received:', msg.event, msg.data) // eslint-disable-line no-console
|
|
||||||
const { subject, count, rating } = msg.data
|
|
||||||
return UPDATE(Books,subject).with({ numberOfReviews:count, rating })
|
|
||||||
})
|
|
||||||
|
|
||||||
//
|
|
||||||
// Reduce stock of ordered books for orders are created from Orders admin UI
|
|
||||||
//
|
|
||||||
OrdersService.on ('OrderChanged', (msg) => {
|
|
||||||
console.debug ('> received:', msg.event, msg.data) // eslint-disable-line no-console
|
|
||||||
const { product, deltaQuantity } = msg.data
|
|
||||||
return UPDATE (Books) .where ('ID =', product)
|
|
||||||
.and ('stock >=', deltaQuantity)
|
|
||||||
.set ('stock -=', deltaQuantity)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// dummy to auto-load the plugin
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using { sap } from '@sap/cds/common';
|
|
||||||
|
|
||||||
extend sap.common.Currencies with {
|
|
||||||
// Currencies.code = ISO 4217 alphabetic three-letter code
|
|
||||||
// with the first two letters being equal to ISO 3166 alphabetic country codes
|
|
||||||
// See also:
|
|
||||||
// [1] https://www.iso.org/iso-4217-currency-codes.html
|
|
||||||
// [2] https://www.currency-iso.org/en/home/tables/table-a1.html
|
|
||||||
// [3] https://www.ibm.com/support/knowledgecenter/en/SSZLC2_7.0.0/com.ibm.commerce.payments.developer.doc/refs/rpylerl2mst97.htm
|
|
||||||
numcode : Integer;
|
|
||||||
exponent : Integer; //> e.g. 2 --> 1 Dollar = 10^2 Cent
|
|
||||||
minor : String; //> e.g. 'Cent'
|
|
||||||
}
|
|
||||||
@@ -5,11 +5,9 @@ CAD;de;Kanadischer Dollar;Kanadischer Dollar
|
|||||||
AUD;de;Australischer Dollar;Australischer Dollar
|
AUD;de;Australischer Dollar;Australischer Dollar
|
||||||
GBP;de;Pfund;Britische Pfund
|
GBP;de;Pfund;Britische Pfund
|
||||||
ILS;de;Schekel;Israelische Schekel
|
ILS;de;Schekel;Israelische Schekel
|
||||||
JPY;de;Yen;Japanische Yen
|
|
||||||
EUR;fr;euro;de la Zone euro
|
EUR;fr;euro;de la Zone euro
|
||||||
USD;fr;dollar;dollar des États-Unis
|
USD;fr;dollar;dollar des États-Unis
|
||||||
CAD;fr;dollar canadien;dollar canadien
|
CAD;fr;dollar canadien;dollar canadien
|
||||||
AUD;fr;dollar australien;dollar australien
|
AUD;fr;dollar australien;dollar australien
|
||||||
GBP;fr;livre sterling;pound sterling
|
GBP;fr;livre sterling;pound sterling
|
||||||
ILS;fr;Shekel;shekel israelien
|
ILS;fr;Shekel;shekel israelien
|
||||||
JPY;fr;Yen;Yen japonais
|
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
code;name
|
|
||||||
g;Grams
|
|
||||||
kg;Kilograms
|
|
||||||
mg;Milligrams
|
|
||||||
lb;Pounds
|
|
||||||
oz;Ounces
|
|
||||||
|
@@ -1,11 +0,0 @@
|
|||||||
code;locale;name
|
|
||||||
g;de;Gramm
|
|
||||||
kg;de;Kilogramm
|
|
||||||
mg;de;Milligramm
|
|
||||||
lb;de;Pfund
|
|
||||||
oz;de;Unze
|
|
||||||
g;fr;Grammes
|
|
||||||
kg;fr;Kilogrammes
|
|
||||||
mg;fr;Milligrammes
|
|
||||||
lb;fr;Livres
|
|
||||||
oz;fr;Onces
|
|
||||||
|
@@ -1,3 +1,45 @@
|
|||||||
using from './currencies';
|
using { sap } from '@sap/cds/common';
|
||||||
using from './regions';
|
|
||||||
using from './uom';
|
extend sap.common.Currencies with {
|
||||||
|
// Currencies.code = ISO 4217 alphabetic three-letter code
|
||||||
|
// with the first two letters being equal to ISO 3166 alphabetic country codes
|
||||||
|
// See also:
|
||||||
|
// [1] https://www.iso.org/iso-4217-currency-codes.html
|
||||||
|
// [2] https://www.currency-iso.org/en/home/tables/table-a1.html
|
||||||
|
// [3] https://www.ibm.com/support/knowledgecenter/en/SSZLC2_7.0.0/com.ibm.commerce.payments.developer.doc/refs/rpylerl2mst97.htm
|
||||||
|
numcode : Integer;
|
||||||
|
exponent : Integer; //> e.g. 2 --> 1 Dollar = 10^2 Cent
|
||||||
|
minor : String; //> e.g. 'Cent'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Code Lists below are designed as optional extensions to
|
||||||
|
* the base schema. Switch them on by adding an Association to
|
||||||
|
* one of the code list entities in your models or by:
|
||||||
|
* annotate sap.common.Countries with @cds.persistence.skip:false;
|
||||||
|
*/
|
||||||
|
|
||||||
|
context sap.common_countries {
|
||||||
|
|
||||||
|
extend sap.common.Countries {
|
||||||
|
regions : Composition of many Regions on regions._parent = $self.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity Regions : sap.common.CodeList {
|
||||||
|
key code : String(5); // ISO 3166-2 alpha5 codes, e.g. DE-BW
|
||||||
|
children : Composition of many Regions on children._parent = $self.code;
|
||||||
|
cities : Composition of many Cities on cities.region = $self;
|
||||||
|
_parent : String(11);
|
||||||
|
}
|
||||||
|
entity Cities : sap.common.CodeList {
|
||||||
|
key code : String(11);
|
||||||
|
region : Association to Regions;
|
||||||
|
districts : Composition of many Districts on districts.city = $self;
|
||||||
|
}
|
||||||
|
entity Districts : sap.common.CodeList {
|
||||||
|
key code : String(11);
|
||||||
|
city : Association to Cities;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,12 +4,5 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": "*"
|
"@sap/cds": "*"
|
||||||
},
|
|
||||||
"cds": {
|
|
||||||
"requires": {
|
|
||||||
"@capire/common/data": {
|
|
||||||
"model": "@capire/common"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
using { sap.common } from '@sap/cds/common';
|
|
||||||
namespace sap.common.countries;
|
|
||||||
|
|
||||||
extend common.Countries {
|
|
||||||
regions : Composition of many Regions on regions._parent = $self.code;
|
|
||||||
}
|
|
||||||
|
|
||||||
entity Regions : common.CodeList {
|
|
||||||
key code : String(5); // ISO 3166-2 alpha5 codes, e.g. DE-BW
|
|
||||||
children : Composition of many Regions on children._parent = $self.code;
|
|
||||||
cities : Composition of many Cities on cities.region = $self;
|
|
||||||
_parent : String(11);
|
|
||||||
}
|
|
||||||
entity Cities : common.CodeList {
|
|
||||||
key code : String(11);
|
|
||||||
region : Association to Regions;
|
|
||||||
districts : Composition of many Districts on districts.city = $self;
|
|
||||||
}
|
|
||||||
entity Districts : common.CodeList {
|
|
||||||
key code : String(11);
|
|
||||||
city : Association to Cities;
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using { sap.common.CodeList } from '@sap/cds/common';
|
|
||||||
namespace sap.common.uom;
|
|
||||||
|
|
||||||
entity PackagingUnits : CodeList {
|
|
||||||
key code : String(3); // e.g. pc for Piece(s)
|
|
||||||
abbrev : localized String(3); // e.g. st for Stück
|
|
||||||
}
|
|
||||||
|
|
||||||
entity Weights : CodeList {
|
|
||||||
key code : String(3); // e.g. kg for Kilogram(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
entity Lengths : CodeList {
|
|
||||||
key code : String(3); // e.g. m for Meter(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PackagingUnit : Association to PackagingUnits;
|
|
||||||
type Length : Association to Lengths;
|
|
||||||
type Weight : Association to Weights;
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import cds from '@sap/cds/eslint.config.mjs'
|
|
||||||
export default [ ...cds.recommended ]
|
|
||||||
|
|
||||||
//
|
|
||||||
// The above is the recommended minimalistic eslint config, just using
|
|
||||||
// recommended defaults provided by cds. Alternatively, go for enhanced
|
|
||||||
// project-specific options, but only if really required, like this:
|
|
||||||
//
|
|
||||||
// export default [ ...cds.recommended, {
|
|
||||||
// rules: {
|
|
||||||
// 'complexity': [ 'warn', 66 ],
|
|
||||||
// 'require-await': 'warn',
|
|
||||||
// 'no-await-in-loop': 'warn',
|
|
||||||
// },
|
|
||||||
// }, {
|
|
||||||
// ignores: [
|
|
||||||
// '**/webapp/**'
|
|
||||||
// ]
|
|
||||||
// }]
|
|
||||||
//
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 67 KiB |
@@ -1,195 +0,0 @@
|
|||||||
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="357px" height="283px" viewBox="-0.5 -0.5 357 283" content="<mxfile><diagram id="QQJxv4aCTC7ZgE7HHOvM" name="Page-1">7VrZcqM4FP0av7q0L4+T9DIvU9VVeZhnFtlQwRYly7HTX98XgzBgOpNJG7eZGlKVwNECuudwdSSyoI+b41cXldlfNjXFgqD0uKCfFoQIIuF3BbzWACO6BtYuT2sInYGn/LupQRzQfZ6aXYPVkLe28HnZBxO73ZrE97DIOXvoV1vZIu0BZbQ2vceogKckKsxFtb/z1Gc1qsKwKvxPk6+zcGcsmvHFUfK8dna/be63IBQxXpdtotBRc9NdFqX20IHo5wV9dNb6+mxzfDRFFdd+zL78pLR9aGe2/j0NSN3gJSr2zbgXDCVRmTtTNSIi2pQL+rCNd9UfuC6g24fYwdnad5EAxNY+7zJbhgLoJT5XPg3Zv4YYw+jL6jQzx2htt1ClNC7fGG/cGf0WIAjOwyHLvXkqo6RqdgDhAZb5TQFXGE5X+dEELVXXu2fjkyri6FRYFI+2sO50c7paGZEkVSXv7LPplKRSxwi1JYF+0g6hG98m5C/GeXPsQE28vxoLD+9eoUpTSiSrm4QXQ9L6+nCWmW7kkXUUphosaoS9bns+8wsnDcXjdNNJ6Pa2an73fKvEjPMdK874zfimktyMb3Ztvp15yQ2k1rtnO+VGpWyMbUViKsRUbFOB+2xjfTO2+bXZTuxmA8TdPdkrXv1UuN36Dl4fYyIQp+NWImDhRbyBCMS1RWBdatwc3vjIqNVofheJMvFqKrKxEr+NbHlJ9pATs03/qOwwXCVFtNvlST/EvZBeBgiaN/GXp/LI+d71INRfTsdbgd3ZvUtML2FBp2vjew7FpMGd/yT4neDykeAGzJki8vlL39OPRby5wzebw/OeuSW4P3lzRPp91MNpmnWN9kVPYmD7+KCnOgoXPZ0k0A78XapQ81aFuHdVwKu75Iq2B+knAKrJslNKqfyYZIhmS4ZFewzXDQgvO6VC0Kn0pOetJzJzPTH+NtHX0hN7W7bX01PY4pmroNjMBcWHgppET3ygJzaZnIJlu5rl3e3Lssj/d73/wvXecp2LyUj+GFD4BJ2A4tD9M2hwys1oVtJC0miyRaoeEHjDbSk8sg85pwkgCLA7A4QsNI8pYOgp5CA7T+QpyGCqueIkMLLVOXdJibtSFAaqJVKcICw1ZIuw+dAuRzQoQSKGKFeSsY+aVIyXRGMmtSJIEBk+jrRrKw2yJpprwTAYmek86shm6qz0FL6Z3q1JxVpAClKnL8MaCc4HmUMuYaUDVhXWuUroD267EFClolhKLhHSlA1uwtmSUsGY4lphitRkarr6ruwqty7/T5ubCyczovl3f3Qjt3Q3974rO0gA/5hL6IjbEb/V3Ei9FFIpyBuKcKF0f5KozA3lEqYQLDimUvyCuaGQSSFzYMkoHtwFvI1gkGGwokwJWHBdKXvA5fkfQOrq5/+woZ9/AA==</diagram></mxfile>" style="background-color: rgb(0, 68, 85);">
|
|
||||||
<defs/>
|
|
||||||
<g>
|
|
||||||
<path d="M 199 202 L 249 202 L 269 242 L 249 282 L 199 282 L 179 242 Z" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 242px; margin-left: 180px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
@capire/
|
|
||||||
<br/>
|
|
||||||
<b>
|
|
||||||
bookshop
|
|
||||||
</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="224" y="246" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
|
||||||
@capire/...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 199 101 L 249 101 L 269 141 L 249 181 L 199 181 L 179 141 Z" fill="#f8cecc" stroke="#b85450" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 141px; margin-left: 180px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
@capire/
|
|
||||||
<br/>
|
|
||||||
<b>
|
|
||||||
bookstore
|
|
||||||
</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="224" y="145" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
|
||||||
@capire/...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 286 48 L 336 48 L 356 88 L 336 128 L 286 128 L 266 88 Z" fill="#d5e8d4" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 267px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
@capire/
|
|
||||||
<br/>
|
|
||||||
<b>
|
|
||||||
reviews
|
|
||||||
</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="311" y="92" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
|
||||||
@capire/...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 286 153 L 336 153 L 356 193 L 336 233 L 286 233 L 266 193 Z" fill="#f5f5f5" stroke="#666666" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 193px; margin-left: 267px;">
|
|
||||||
<div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
@capire/
|
|
||||||
<br/>
|
|
||||||
<b>
|
|
||||||
common
|
|
||||||
</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="311" y="197" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
|
||||||
@capire/...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 111 153 L 161 153 L 181 193 L 161 233 L 111 233 L 91 193 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 193px; margin-left: 92px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
@capire/
|
|
||||||
<br/>
|
|
||||||
<b>
|
|
||||||
orders
|
|
||||||
</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="136" y="197" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
|
||||||
@capire/...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 276.35 172.29 L 266.36 166.32" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 260.57 162.86 L 270.6 163.61 L 266.36 166.32 L 265.98 171.34 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 170.74 172.47 L 181.53 166.1" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 187.34 162.66 L 181.88 171.12 L 181.53 166.1 L 177.3 163.37 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 224 202 L 224 189.99" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 224 183.24 L 228.5 192.24 L 224 189.99 L 219.5 192.24 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 276.51 109.01 L 266.17 115.31" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 260.4 118.82 L 265.75 110.3 L 266.17 115.31 L 270.43 117.98 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 111 48 L 161 48 L 181 88 L 161 128 L 111 128 L 91 88 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 92px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
@capire/
|
|
||||||
<br/>
|
|
||||||
<b>
|
|
||||||
suppliers
|
|
||||||
</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="136" y="92" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
|
||||||
@capire/...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 21 101 L 71 101 L 91 141 L 71 181 L 21 181 L 1 141 Z" fill="#e1d5e7" stroke="#9673a6" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 141px; margin-left: 2px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
<b>
|
|
||||||
S/4
|
|
||||||
</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="46" y="145" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
|
||||||
S/4
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 80.76 120.53 L 93.49 113.03" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 99.31 109.61 L 93.84 118.05 L 93.49 113.03 L 89.27 110.3 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 80.91 161.17 L 93.31 168.33" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 99.15 171.71 L 89.11 171.1 L 93.31 168.33 L 93.61 163.31 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 170.59 108.83 L 181.72 115.53" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 187.5 119.02 L 177.47 118.23 L 181.72 115.53 L 182.11 110.52 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 199 1 L 249 1 L 269 41 L 249 81 L 199 81 L 179 41 Z" fill="#e1d5e7" stroke="#9673a6" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 180px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
@capire/
|
|
||||||
<br/>
|
|
||||||
<b>
|
|
||||||
fiori
|
|
||||||
</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="224" y="45" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
|
||||||
@capire/...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 224 101 L 224 89.99" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 224 83.24 L 228.5 92.24 L 224 89.99 L 219.5 92.24 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%">
|
|
||||||
Text is not SVG - cannot display
|
|
||||||
</text>
|
|
||||||
</a>
|
|
||||||
</switch>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,119 +0,0 @@
|
|||||||
/* global Vue axios */ //> from vue.html
|
|
||||||
const GET = (url) => axios.get('/odata/v4/-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()
|
|
||||||
@@ -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 – {{ 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 + ' – ' + 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>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
const cds = require("@sap/cds")
|
|
||||||
cds.on ('served', ()=> {
|
|
||||||
cds.app.serve ('/data') .from ('@capire/data-viewer','app/viewer')
|
|
||||||
})
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
using from './srv/data-service';
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
/**
|
|
||||||
* Exposes data + entity metadata
|
|
||||||
*/
|
|
||||||
@requires:'authenticated-user'
|
|
||||||
@odata service DataService @( path:'-data' ) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metadata like name and columns/elements
|
|
||||||
*/
|
|
||||||
entity Entities @cds.persistence.skip {
|
|
||||||
key name : String;
|
|
||||||
columns: Composition of many {
|
|
||||||
name : String;
|
|
||||||
type : String;
|
|
||||||
isKey: Boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The actual data, organized by column name
|
|
||||||
*/
|
|
||||||
entity Data @cds.persistence.skip {
|
|
||||||
key ID : String; // to be OData-compliant
|
|
||||||
record : array of {
|
|
||||||
column : String;
|
|
||||||
data : String;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,63 +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)
|
|
||||||
let res = await dataSource.run(query)
|
|
||||||
if (!Array.isArray(res)) res = [res] // singleton result
|
|
||||||
return res.map((line) => {
|
|
||||||
const record = Object.entries(line).map(([column, data]) => ({ column, data }))
|
|
||||||
return {
|
|
||||||
record,
|
|
||||||
ID: cds.utils.uuid() // just to be OData-compliant
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return super.init()
|
|
||||||
}}
|
|
||||||
|
|
||||||
module.exports = { DataService }
|
|
||||||
|
|
||||||
/** @returns {cds.Service} */
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -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="<mxfile><diagram id="QQJxv4aCTC7ZgE7HHOvM" name="Page-1">7Vpbc9soFP41fqxHCF0f7VyanWl2O3W3fcYSltkg4UH41l+/EIEtGdl1HTlRM508RBzDMfB9h/NxrAG8yTcfOVrMH1mK6cB10s0A3g5cFwAQy3/Ksq0sgQcrQ8ZJWpmcvWFCfmA90liXJMWltlUmwRgVZNE0JqwocCIaNsQ5Wze7zRhNG4YFynBjGsowSRDFVrfvJBXzyhq54d7+gEk2N98MAr3gHJnO2nE5Rylb10zwbgBvOGOieso3N5iqzWvuy/2RT3cT47gQ5wxwqwErRJd6bRPZ2ZOmh9HfIz1HsTUL52xZpFiNdQZwvJ4TgScLlKhP1xJqaZuLnMoWkI8Jy0miu5ZPWCRz3fiPZNmzR082ZqwQ9ygnVHHhAdMVFiRBaojg7Gm3v3JnxoiSrJDPvNra8Qpz1ZeOtF0wNYNSTogUmWz7Oy83jDL+vAQYuepPzZRx8kN+OaJmjtXALxq4UE9uopcPdLvmy7tVf9Jub7tGQk0Rb2omDcNHzHIs+FZ20Z9+AIHmhA4KYDiy3lMMxH5lm9fpZVBEmtbZzvkeefmgwW8nArSIMF6WpMBlOVDzCKja8CmXT5l6+oy4KDA/QRDwSgSpwTGNfM93WqkzI5TWes6iBCdJV8CBaOg3ofNjGzoDZx0633k5cp6F3A4ug9ZfRSIPTLlC13nMclFDVDrf9bJRvtsIXJSEFXLEaLHo6XFA8eyc0yA+eRpcMbLPiGvohTY5oBe/nB2+xQ5DhrJfwRskEZ7OzgneFOFo1lHwun7YBCd27cgFLYduF5EbtETuASS4SEdKsMhWSlDOivTrnEh2j+UH94QaMGRLqyQQNGG6KgClkGnAzO/uS46KrbHqyTmmracXn4KtZEueGIml90KOzbDZ88qEU6POjmBbx84EH8cUCbJqCrg28LS7z4w8n5cmhr0GTTzgmyPf+Kgmr4fVFdeBp92BfdxVtWbL1TOddss8i2GhxbCbZSmkB96z6E/DeOqcl7pnOOgqdcdNKFqD/1ppO7KgmSyTROqte5QIdhKf3197t0htS46/pfZ2g8BmQhwMWxKB63SgvmOLDN8Zf5oxdRy2ym8ZwEqV9SmEe6O+oQNfL4xNSaIG3V2+oGyL25H7SnJczjEWf8BrBy9qibyrgQcs8E4psCllyZNceYokggaovRBzmkLMvVSIdZkL65IK2ooqfENF1RTePggv1VM/cdSdmjKatEaXR5mwUYZ7pqb6cZeCDrBD+Vp3KWAXsE6Fcl8vU7Vr0nmXq7Mi3+9V5LshbPIEwgtD3/yOcMxRh6FvV9kmmK/IEX2m1FvfqqO9uGK9rjazi1/vOL3HdpAb1r5FlINDYQeDSxN8fOAq8A9m02Gc/1pNrmAFPkWS3uQPtsDFpdnDsKgnpTgQuEMnigNVlAO+E0O3QQ43iC/jGQjg0AMODEPoR17sBFHTL7hecrHLdCaBtOSW0QoRiqaEErH9k2HsDPOqF0i7iveOM4ypdTRSzBsKSWDlGO/CHAMc77SjDoPdrvWNUfIkcZdGrSnt66Qpt35CU0w/s5II9ausZBQTguUKavMiiVPnl1Or3CYSWnmkNDh1pIpr0UZ6X6iZ5JtMvdEzzCWPl4thjrj6lyw53Y758zFzorT0wuOGM4H0qp0TZakZJYtvenkdnC4fwMHvNb7Tcr5AYJ8vbgfni2tXF/9V9QZp+kbw+l3xZHdqXZcn5sTrmCfRYSHzdXliFzLr727cIoHeE1V26viqVNkp646p4nrxGzLFrmFazDDQJFtKpJrk8OdSclrpzk/TnUHmtOxZjf6zFNIN1vayEjfuMIaxH4cQ+B4MXAgOATx+yzn/ptQBViBqYgXi0MYqtqEC/i9DJZv79z4rYbF/exbe/Q8=</diagram></mxfile>">
|
|
||||||
<defs/>
|
|
||||||
<g>
|
|
||||||
<rect x="1" y="1" width="195" height="122" fill="rgb(255, 255, 255)" stroke="#828282" stroke-width="2" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)rotate(-90 11 13)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: #4D4D4D; " style="box-sizing: border-box; font-size: 0px; text-align: right;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(77, 77, 77); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-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 pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
Business
|
|
||||||
<br/>
|
|
||||||
Partner
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="103" y="69" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
|
||||||
Business...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<rect x="221" y="1" width="347" height="349" fill="rgb(255, 255, 255)" stroke="#828282" stroke-width="2" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: #4D4D4D; " style="box-sizing: border-box; font-size: 0px; text-align: left;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(77, 77, 77); line-height: 1.2; pointer-events: all; white-space: normal; overflow-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 pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
Incidents
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="476" y="102" fill="rgb(0, 0, 0)" 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 pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
Customers
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="315" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
|
||||||
Customers
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<rect x="1" y="147" width="196.5" height="202" fill="rgb(255, 255, 255)" stroke="#828282" stroke-width="2" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)rotate(-90 11 159)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: #4D4D4D; " style="box-sizing: border-box; font-size: 0px; text-align: right;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(77, 77, 77); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-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 pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
Workforce
|
|
||||||
<br/>
|
|
||||||
Person
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="103" y="213" fill="rgb(0, 0, 0)" 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 pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
Employee
|
|
||||||
<br/>
|
|
||||||
Timesheet
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="103" y="296" fill="rgb(0, 0, 0)" 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 pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
Messages
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="476" y="211" fill="rgb(0, 0, 0)" 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 pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
Service
|
|
||||||
<br/>
|
|
||||||
Worker
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="315" y="213" fill="rgb(0, 0, 0)" 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 pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
Worker
|
|
||||||
<br/>
|
|
||||||
Availability
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="315" y="296" fill="rgb(0, 0, 0)" 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 pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">
|
|
||||||
Backend Services
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="103" y="426" fill="rgb(0, 0, 0)" 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 pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">
|
|
||||||
Usage Views
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="315" y="426" fill="rgb(0, 0, 0)" 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 pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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 data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">
|
|
||||||
Extension Data
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="476" y="426" fill="rgb(0, 0, 0)" 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%">
|
|
||||||
Text is not SVG - cannot display
|
|
||||||
</text>
|
|
||||||
</a>
|
|
||||||
</switch>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 273 KiB |
@@ -1,76 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title> cds.log </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>
|
|
||||||
<style>
|
|
||||||
select { border-color: transparent; padding: 4px 12px; margin: 0px; }
|
|
||||||
button { padding: 2px 11px; margin: 0px 4px; font: 90% italic; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="small-container" , style="margin-top: 70px;">
|
|
||||||
<div id='app'>
|
|
||||||
<h1> Log Levels </h1>
|
|
||||||
<input type="text" placeholder="Search by ID or Log Level..." @input="fetch">
|
|
||||||
<table id='loggers'>
|
|
||||||
<thead>
|
|
||||||
<th> Module ID </th>
|
|
||||||
<th> Log Level </th>
|
|
||||||
</thead>
|
|
||||||
<tr v-for="each in list">
|
|
||||||
<td>{{ each.id }}</td>
|
|
||||||
<td><select v-bind:id="each.id" v-model="each.level" @change="set">
|
|
||||||
<option>SILENT</option>
|
|
||||||
<option>ERROR</option>
|
|
||||||
<option>WARN</option>
|
|
||||||
<option>INFO</option>
|
|
||||||
<option>DEBUG</option>
|
|
||||||
<option>TRACE</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<h4>Log Format:</h4>
|
|
||||||
[ <button class="round-button" :class={'muted-button':!format.timestamp} @click="toggle_format" id="timestamp">Timestamp </button>
|
|
||||||
| <button class="round-button" :class={'muted-button':!format.level} @click="toggle_format" id="level">Log Level </button>
|
|
||||||
| <button class="round-button" :class={'muted-button':!format.tenant} @click="toggle_format" id="tenant">Tenant </button>
|
|
||||||
| <button class="round-button" :class={'muted-button':!format.reqid} @click="toggle_format" id="reqid">Request ID </button>
|
|
||||||
| <button class="round-button" :class={'muted-button':!format.id} @click="toggle_format" id="module">Logger ID </button>
|
|
||||||
] - <i>log message ...</i>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
axios.defaults.headers['Content-Type'] = 'application/json'
|
|
||||||
axios.defaults.baseURL = '/log'
|
|
||||||
const loggers = Vue.createApp({ el: '#app',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
format: { timestamp:false, level:false, tenant:false, reqid:false, id:true, },
|
|
||||||
list: [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetch (eve) {
|
|
||||||
this.list = (await axios.get (`/Loggers${
|
|
||||||
eve && eve.target.value ? `?$search=${eve.target.value}` : ''
|
|
||||||
}`)).data
|
|
||||||
},
|
|
||||||
async set (eve) {
|
|
||||||
const { id, value:level } = eve.target
|
|
||||||
await axios.put (`/Logger/${id}`, {id,level})
|
|
||||||
},
|
|
||||||
async toggle_format (eve) {
|
|
||||||
this.format[eve.target.id] = !this.format[eve.target.id]
|
|
||||||
await axios.post (`/format`, this.format)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).mount('#app')
|
|
||||||
loggers.fetch() // initially fill list of loggers
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@capire/loggers",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Simple sample on how to dynamically set cds.log levels and formats.",
|
|
||||||
"files": [
|
|
||||||
"app",
|
|
||||||
"srv"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"@sap/cds": ">=5.9",
|
|
||||||
"express": "^4.17.1"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "cds-serve",
|
|
||||||
"watch": "cds watch"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# Dynamically Set `cds.log` Levels and Formats
|
|
||||||
|
|
||||||
### Run
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cds watch
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test
|
|
||||||
|
|
||||||
Either using the UI through http://localhost:4004/loggers.html, or try the requests in `test/requests.http`
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
service Sue {
|
|
||||||
entity Dummy { key ID: UUID; title: String; }
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
@rest service LogService {
|
|
||||||
|
|
||||||
@readonly entity Loggers : Logger {};
|
|
||||||
entity Logger {
|
|
||||||
key id : String;
|
|
||||||
level : String;
|
|
||||||
}
|
|
||||||
|
|
||||||
action format (
|
|
||||||
timestamp : Boolean,
|
|
||||||
level : Boolean,
|
|
||||||
tenant : Boolean,
|
|
||||||
reqid : Boolean,
|
|
||||||
id : Boolean,
|
|
||||||
);
|
|
||||||
|
|
||||||
action debug (logger : String) returns Logger;
|
|
||||||
action reset (logger : String) returns Logger;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
const cds = require ('@sap/cds')
|
|
||||||
const LOG = cds.log('cds.log')
|
|
||||||
|
|
||||||
module.exports = class LogService extends cds.Service {
|
|
||||||
init(){
|
|
||||||
|
|
||||||
this.on('GET','Loggers', (req)=>{
|
|
||||||
let loggers = Object.values(cds.log.loggers).map (_logger)
|
|
||||||
let {$search} = req._.req.query
|
|
||||||
if ($search) {
|
|
||||||
const re = RegExp($search,'i')
|
|
||||||
loggers = loggers.filter (l => re.test(l.id) || re.test(l.level))
|
|
||||||
}
|
|
||||||
return loggers.sort ((a,b) => a.id < b.id ? -1 : 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.on('PUT','Logger', (req)=>{
|
|
||||||
const {id} = req.params[0] || req.data
|
|
||||||
if (!id) return req.reject('No logger id specified in request')
|
|
||||||
return _logger (cds.log (id, req.data))
|
|
||||||
})
|
|
||||||
|
|
||||||
this.on('debug', (req)=>{
|
|
||||||
const {logger:id} = req.params[0] || req.data
|
|
||||||
if (!id) return req.reject('No logger id specified in request')
|
|
||||||
return _logger (cds.log (id, {level:'debug'}))
|
|
||||||
})
|
|
||||||
|
|
||||||
this.on('reset', (req)=>{
|
|
||||||
const {logger:id} = req.params[0] || req.data
|
|
||||||
if (!id) return req.reject('No logger id specified in request')
|
|
||||||
return _logger (cds.log (id, {level:'info'}))
|
|
||||||
})
|
|
||||||
|
|
||||||
this.on('format', (req)=>{
|
|
||||||
const $ = req.data; LOG.info('format:',$)
|
|
||||||
// Set format for new loggers constructed subsequently
|
|
||||||
cds.log.format = (id, level, ...args) => {
|
|
||||||
const fmt = []
|
|
||||||
if ($.timestamp) fmt.push ('|', (new Date).toISOString())
|
|
||||||
if ($.level) fmt.push ('|', _levels[level].padEnd(5))
|
|
||||||
if ($.tenant) fmt.push ('|', cds.context && cds.context.tenant)
|
|
||||||
if ($.reqid) fmt.push ('|', cds.context && cds.context.id)
|
|
||||||
if ($.id) fmt.push ('|', id)
|
|
||||||
fmt[0] = '[', fmt.push ('] -', ...args)
|
|
||||||
return fmt
|
|
||||||
}
|
|
||||||
// Apply this format to all existing loggers
|
|
||||||
Object.values(cds.log.loggers).forEach (l => l.setFormat (cds.log.format))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const _logger = ({id,level}) => ({id, level:_levels[level] })
|
|
||||||
const _levels = [ 'SILENT', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE' ]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
http://localhost:4004/loggers.html
|
|
||||||
@body: = Content-Type: application/json\n\n
|
|
||||||
|
|
||||||
###
|
|
||||||
GET http://localhost:4004/log/Loggers
|
|
||||||
|
|
||||||
###
|
|
||||||
PUT http://localhost:4004/log/Logger/sqlite
|
|
||||||
{{body:}} { "level": "debug" }
|
|
||||||
|
|
||||||
###
|
|
||||||
POST http://localhost:4004/log/debug(logger='sqlite')
|
|
||||||
|
|
||||||
###
|
|
||||||
POST http://localhost:4004/log/reset(logger='sqlite')
|
|
||||||
|
|
||||||
### Dummy request to see sqlite debug output
|
|
||||||
GET http://localhost:4004/sue/Dummy
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="368px" height="282px" viewBox="-0.5 -0.5 368 282" content="<mxfile><diagram id="QQJxv4aCTC7ZgE7HHOvM" name="Page-1">7Zpbc9o4FIB/DY9hdL88JqRJH9ppd9mZndk3YwvwxCBXmIT01+9xLNvYMaEl4ZIUeLB1JOt2viMdXXp0MFvduiCdfrWRSXoERaseve4RgpnU8Mglj4VEIF4IJi6OChGqBcP4p/FfltJlHJmFlxWizNoki9OmMLTzuQmzhixwzj40k41tEjUEaTAxzwTDMEieS/+No2xaSBWRtfyziSfTsmQsfINnQZnYt2QxDSL7sCain3p04KzNirfZamCSvPOa/XKzIbaqmDPz7Fc+mJDb2+X19NuX/778vEB/LX+gO3vhc7kPkqVvcI+IBPK7GuVVzh59P4gfy7yeV2M7zy4WT1q6hAQYp6Dpqzoe3ib588rau8XUpmVuUK1RGVeV4F5VxGWa+o6tsiDQx2n+OjWrYGLnkCo1Lp6ZzLha+r0UgQquHqZxZoZpEOafPQDDIJtmswRCOK9MvDIllUU4SQY2se6pPDoeGxGGIF9kzt6ZtZhI6hFCVUzJDvENvAlmcZKbxMDO4hAaMQzmC3h8HfoEZZm4DPtGoqceaGu+VKNxmVmtiTwJt8ZCg90jJPGxhPtPvFlSLYrwQw259uBO1/hWXhZ4s5pUOdfkwYuH7zdAJBtBXKTB/FWgvHlGowblmXVmHelm2i7wQVbUZQ3kNzGJDQUc30xUaLrNZKQ443syE+iCMJ5P/oFRiF7z/dgN0fK4dkP3bzevmwn+NvexgVl4fxPB0Lj7ODQf3IYiblTEumxIkREVYh829Hp7oaxlL0wd117YSw5PoWrop5nNTWcbs4XkOsiCo9NhMPAhu+jQQtLgndBBGT8uHfzUR9NvLjLuPJj+mYMpFidmLqLDXFqaNfPoMl+GQyhMgsUiDpuKanZzLoEvfCcW41ngska4pTA+yP/7U8/CLl1ots8oUM+JybavcEzU2HB4ruw1ZfIOZZYyZ5Igi++b2xRdGvYlfLcxtKxmCesmTKzc0ijzKBruP1vfV2jnRNqjuGzlVHTNs5yekKsavjuF8kxhNXO9MwoJl32uaPUjokkSlv21WEp3RJRI3GdYVD/JmsUQ0V+LFYIelF915rfaiPxg/MqXwXorfsXLZrJnfvWZ32of5mPxyw7DLzsuv+VR0+mu+obLNE3i88LvvPDrncYuGt58bvhqkxlCjdjJ8hMYNe48yRChMqPxRn7aeGznyTey45z3t/kRvOkvIHxkfMjZZaiNaKvPUE5Q78NpaDu95WS/Z6e3rMWhnIaug7czwpsS8pMiGANaEikOs5DUVJJy66tEizIgTyKGKFeSsR29XqxUn+j8CpQiSBApabMUwsCMiOZaMEw4P7DT23UQ9ufxi36R39NatmFJYYhV+aat1kjw1vQuSR+WapxjhrASmuyKr4ABFkvJJUK6OmuoCtF9SgVjimuFKVKHpffkD+puYuvifd9++9BLtfF4TDbcrRMjwfeyVDvMpSF87Mt2+HxwV4/p293v03JeCBd9wmE8Fk+neKp1+IZ1n2kYjyV4FAIcmJ237FBfMsEZlVIphHXLd4ElABIaE8byKyak5ePvPPxDsL4RXSSv75XTT/8D</diagram></mxfile>">
|
|
||||||
<defs/>
|
|
||||||
<g>
|
|
||||||
<path d="M 207 201 L 257 201 L 277 241 L 257 281 L 207 281 L 187 241 Z" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 241px; margin-left: 188px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
<b style="font-size: 11px;">
|
|
||||||
Bookshop
|
|
||||||
</b>
|
|
||||||
<br style="font-size: 11px;"/>
|
|
||||||
App
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="232" y="244" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
|
||||||
Bookshop...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 207 102 L 257 102 L 277 142 L 257 182 L 207 182 L 187 142 Z" fill="#f8cecc" stroke="#b85450" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 145px; margin-left: 188px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
<span style="font-size: 11px;">
|
|
||||||
<span style="font-size: 11px;">
|
|
||||||
<b>
|
|
||||||
Bookstore
|
|
||||||
<br/>
|
|
||||||
</b>
|
|
||||||
</span>
|
|
||||||
App
|
|
||||||
<br style="font-size: 11px;"/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="232" y="148" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
|
||||||
Bookstore...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 297 53 L 347 53 L 367 93 L 347 133 L 297 133 L 277 93 Z" fill="#d5e8d4" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 278px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
<span style="font-size: 11px;">
|
|
||||||
<b style="font-size: 11px;">
|
|
||||||
Reviews
|
|
||||||
</b>
|
|
||||||
<br style="font-size: 11px;"/>
|
|
||||||
Service
|
|
||||||
<br style="font-size: 11px;"/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="322" y="96" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
|
||||||
Reviews...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 297 150 L 347 150 L 367 190 L 347 230 L 297 230 L 277 190 Z" fill="#e1d5e7" stroke="#9673a6" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 190px; margin-left: 278px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
<b>
|
|
||||||
Common
|
|
||||||
</b>
|
|
||||||
<br/>
|
|
||||||
Data
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="322" y="193" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
|
||||||
Common...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 117 150 L 167 150 L 187 190 L 167 230 L 117 230 L 97 190 Z" fill="#d5e8d4" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 190px; margin-left: 98px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
<span style="font-size: 11px;">
|
|
||||||
<b style="font-size: 11px;">
|
|
||||||
Orders
|
|
||||||
</b>
|
|
||||||
<br style="font-size: 11px;"/>
|
|
||||||
Service
|
|
||||||
<br style="font-size: 11px;"/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="142" y="193" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
|
||||||
Orders...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 286.47 171.05 L 273.81 164.3" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 268.51 161.47 L 277.45 161.71 L 273.81 164.3 L 273.69 168.77 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 177.53 171.05 L 190.19 164.3" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 195.49 161.47 L 190.31 168.77 L 190.19 164.3 L 186.55 161.71 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 232 201 L 232 189.12" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 232 183.12 L 236 191.12 L 232 189.12 L 228 191.12 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 286.63 112.26 L 273.62 119.34" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 268.35 122.21 L 273.47 114.87 L 273.62 119.34 L 277.29 121.9 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 117 53 L 167 53 L 187 93 L 167 133 L 117 133 L 97 93 Z" fill="#d5e8d4" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 98px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
<span style="font-size: 11px;">
|
|
||||||
<b style="font-size: 11px;">
|
|
||||||
Suppliers
|
|
||||||
</b>
|
|
||||||
<br style="font-size: 11px;"/>
|
|
||||||
Service
|
|
||||||
<br style="font-size: 11px;"/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="142" y="96" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
|
||||||
Suppliers...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 21 106 L 71 106 L 91 146 L 71 186 L 21 186 L 1 146 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 146px; margin-left: 2px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;">
|
|
||||||
<span style="font-size: 11px;">
|
|
||||||
S/4
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="46" y="149" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle" font-weight="bold">
|
|
||||||
S/4
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 81.27 126.53 L 100.5 115.91" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 105.76 113.01 L 100.69 120.38 L 100.5 115.91 L 96.82 113.37 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 82.61 162.78 L 98.92 170.25" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 104.37 172.75 L 95.43 173.06 L 98.92 170.25 L 98.77 165.79 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 177.37 112.26 L 190.38 119.34" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 195.65 122.21 L 186.71 121.9 L 190.38 119.34 L 190.53 114.87 Z" fill="#5c5c5c" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<path d="M 207 1 L 257 1 L 277 41 L 257 81 L 207 81 L 187 41 Z" fill="#fff2cc" stroke="#d6b656" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
|
|
||||||
<g transform="translate(-0.5 -0.5)">
|
|
||||||
<switch>
|
|
||||||
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
|
||||||
<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: 44px; margin-left: 188px;">
|
|
||||||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
|
||||||
<div style="display: inline-block; font-size: 11px; font-family: "Comic Sans MS"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
|
||||||
<span style="font-size: 11px;">
|
|
||||||
<b style="font-size: 11px;">
|
|
||||||
Fiori
|
|
||||||
</b>
|
|
||||||
<br style="font-size: 11px;"/>
|
|
||||||
App
|
|
||||||
<br style="font-size: 11px;"/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</foreignObject>
|
|
||||||
<text x="232" y="47" fill="rgb(0, 0, 0)" font-family="Comic Sans MS" font-size="11px" text-anchor="middle">
|
|
||||||
Fiori...
|
|
||||||
</text>
|
|
||||||
</switch>
|
|
||||||
</g>
|
|
||||||
<path d="M 232 102 L 232 88.12" fill="none" stroke="#5c5c5c" stroke-miterlimit="10" pointer-events="stroke"/>
|
|
||||||
<path d="M 232 82.12 L 236 90.12 L 232 88.12 L 228 90.12 Z" fill="#5c5c5c" stroke="#5c5c5c" 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%">
|
|
||||||
Text is not SVG - cannot display
|
|
||||||
</text>
|
|
||||||
</a>
|
|
||||||
</switch>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "1.1.0",
|
|
||||||
"options": {
|
|
||||||
"management": true,
|
|
||||||
"messagingrest": true,
|
|
||||||
"messaging": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"topicRules": {
|
|
||||||
"publishFilter": [
|
|
||||||
"*"
|
|
||||||
],
|
|
||||||
"subscribeFilter": [
|
|
||||||
"*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"queueRules": {
|
|
||||||
"publishFilter": [
|
|
||||||
"*"
|
|
||||||
],
|
|
||||||
"subscribeFilter": [
|
|
||||||
"*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"authorities": [
|
|
||||||
"$ACCEPT_GRANTED_AUTHORITIES"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1 +1,2 @@
|
|||||||
cds.requires.[hybrid].messaging.kind=file-based-messaging
|
# cds.requires.messaging.kind = file-based-messaging
|
||||||
|
PORT = 4004
|
||||||
@@ -2,20 +2,25 @@ Books = Books
|
|||||||
Book = Book
|
Book = Book
|
||||||
ID = ID
|
ID = ID
|
||||||
Title = Title
|
Title = Title
|
||||||
Description = Description
|
|
||||||
Stock = Stock
|
|
||||||
Image = Image
|
|
||||||
Price = Price
|
|
||||||
Currency = Currency
|
|
||||||
|
|
||||||
Authors = Authors
|
|
||||||
Author = Author
|
Author = Author
|
||||||
AuthorID = Author''s ID
|
AuthorID = Author ID
|
||||||
|
Stock = Stock
|
||||||
Name = Name
|
Name = Name
|
||||||
|
AuthorName = Author's Name
|
||||||
DateOfBirth = Date of Birth
|
DateOfBirth = Date of Birth
|
||||||
DateOfDeath = Date of Death
|
DateOfDeath = Date of Death
|
||||||
PlaceOfBirth = Place of Birth
|
PlaceOfBirth = Place of Birth
|
||||||
PlaceOfDeath = Place of Death
|
PlaceOfDeath = Place of Death
|
||||||
|
Age = Age
|
||||||
|
Authors = Authors
|
||||||
|
Order = Order
|
||||||
|
Orders = Orders
|
||||||
|
Price = Price
|
||||||
|
|
||||||
Genres = Genres
|
|
||||||
Genre = Genre
|
Genre = Genre
|
||||||
|
Genres = Genres
|
||||||
|
SubGenres = Sub Genres
|
||||||
|
|
||||||
|
NumCode = Numeric Code
|
||||||
|
MinorUnit = Minor Unit
|
||||||
|
Exponent = Exponent
|
||||||
@@ -1,5 +1,14 @@
|
|||||||
|
Books = Bücher
|
||||||
|
Book = Buch
|
||||||
|
ID = ID
|
||||||
|
Title = Titel
|
||||||
|
Authors = Autoren
|
||||||
|
Author = Autor
|
||||||
|
AuthorID = ID des Autors
|
||||||
AuthorName = Name des Autors
|
AuthorName = Name des Autors
|
||||||
Age = Alter
|
Age = Alter
|
||||||
rder = Bestellung
|
Name = Name
|
||||||
|
Stock = Bestand
|
||||||
|
Order = Bestellung
|
||||||
Orders = Bestellungen
|
Orders = Bestellungen
|
||||||
Price = Preis
|
Price = Preis
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
Age = Age
|
|
||||||
Lifetime = Lifetime
|
|
||||||
|
|
||||||
SubGenres = Subgenre
|
|
||||||
|
|
||||||
NumCode = Numeric Code
|
|
||||||
MinorUnit = Minor Unit
|
|
||||||
Exponent = Exponent
|
|
||||||
@@ -1,52 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
annotate AdminService.Authors with {
|
|
||||||
age @Common.Label : '{i18n>Age}';
|
|
||||||
lifetime @Common.Label : '{i18n>Lifetime}'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround for Fiori popup for asking user to enter a new UUID on Create
|
|
||||||
annotate AdminService.Authors with { ID @Core.Computed; }
|
|
||||||
@@ -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 */
|
|
||||||
@@ -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=Manage Authors
|
|
||||||
|
|
||||||
# JCI app descriptor contains lower case DESCRIPTION
|
|
||||||
appSubTitle=CAP Sample Application
|
|
||||||
|
|
||||||
# JCI app descriptor contains lower case DESCRIPTION
|
|
||||||
appDescription=Bookshop Authors
|
|
||||||
@@ -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": "manage",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
using { AdminService } from '@capire/bookstore';
|
|
||||||
using from '../common'; // to help UI linter get the complete annotations
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Books Object Page
|
|
||||||
//
|
|
||||||
|
|
||||||
annotate AdminService.Books with @(
|
|
||||||
UI: {
|
|
||||||
Facets: [
|
|
||||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>General}', Target: '@UI.FieldGroup#General'},
|
|
||||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Translations}', Target: 'texts/@UI.LineItem'},
|
|
||||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
|
|
||||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Admin}', Target: '@UI.FieldGroup#Admin'},
|
|
||||||
],
|
|
||||||
FieldGroup#General: {
|
|
||||||
Data: [
|
|
||||||
{Value: title},
|
|
||||||
{Value: author_ID},
|
|
||||||
{Value: genre_ID},
|
|
||||||
{Value: descr},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
FieldGroup#Details: {
|
|
||||||
Data: [
|
|
||||||
{Value: stock},
|
|
||||||
{Value: price},
|
|
||||||
{Value: currency_code, Label: '{i18n>Currency}'},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
FieldGroup#Admin: {
|
|
||||||
Data: [
|
|
||||||
{Value: createdBy},
|
|
||||||
{Value: createdAt},
|
|
||||||
{Value: modifiedBy},
|
|
||||||
{Value: modifiedAt}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Value Help for Tree Table
|
|
||||||
//
|
|
||||||
annotate AdminService.Books with {
|
|
||||||
genre @(Common: {
|
|
||||||
Label : 'Genre',
|
|
||||||
ValueList: {
|
|
||||||
CollectionPath : 'Genres',
|
|
||||||
Parameters : [
|
|
||||||
{
|
|
||||||
$Type : 'Common.ValueListParameterDisplayOnly',
|
|
||||||
ValueListProperty: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$Type : 'Common.ValueListParameterInOut',
|
|
||||||
LocalDataProperty: genre_ID,
|
|
||||||
ValueListProperty: 'ID',
|
|
||||||
}
|
|
||||||
],
|
|
||||||
PresentationVariantQualifier: 'VH',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
annotate AdminService.Genres with @UI: {
|
|
||||||
PresentationVariant #VH: {
|
|
||||||
$Type : 'UI.PresentationVariantType',
|
|
||||||
Visualizations : ['@UI.LineItem'],
|
|
||||||
RecursiveHierarchyQualifier: 'GenreHierarchy'
|
|
||||||
},
|
|
||||||
LineItem : [{
|
|
||||||
$Type: 'UI.DataField',
|
|
||||||
Value: name,
|
|
||||||
Label :'{i18n>Name}'
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hide ID because of the ValueHelp
|
|
||||||
annotate AdminService.Genres with {
|
|
||||||
ID @UI.Hidden;
|
|
||||||
};
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Draft for Localized Data
|
|
||||||
//
|
|
||||||
|
|
||||||
annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
|
|
||||||
annotate AdminService.Books with @odata.draft.enabled;
|
|
||||||
|
|
||||||
annotate AdminService.Books.texts with @(
|
|
||||||
UI: {
|
|
||||||
Identification: [{Value:title}],
|
|
||||||
SelectionFields: [ locale, title ],
|
|
||||||
LineItem: [
|
|
||||||
{Value: locale, Label: 'Locale'},
|
|
||||||
{Value: title, Label: 'Title'},
|
|
||||||
{Value: descr, Label: 'Description'},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
annotate AdminService.Books.texts with {
|
|
||||||
ID @UI.Hidden;
|
|
||||||
ID_texts @UI.Hidden;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add Value Help for Locales
|
|
||||||
annotate AdminService.Books.texts {
|
|
||||||
locale @(
|
|
||||||
ValueList.entity:'Languages', Common.ValueListWithFixedValues, //show as drop down, not a dialog
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// In addition we need to expose Languages through AdminService as a target for ValueList
|
|
||||||
using { sap } from '@sap/cds/common';
|
|
||||||
extend service AdminService {
|
|
||||||
@readonly entity Languages as projection on sap.common.Languages;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround for Fiori popup for asking user to enter a new UUID on Create
|
|
||||||
annotate AdminService.Books with { ID @Core.Computed; }
|
|
||||||
|
|
||||||
55
fiori/app/admin-fiori.html
Normal file
55
fiori/app/admin-fiori.html
Normal 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>
|
||||||
93
fiori/app/admin/fiori-service.cds
Normal file
93
fiori/app/admin/fiori-service.cds
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
using { AdminService } from '../../db/schema';
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Books Object Page
|
||||||
|
//
|
||||||
|
|
||||||
|
annotate AdminService.Books with @(
|
||||||
|
UI: {
|
||||||
|
Facets: [
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>General}', Target: '@UI.FieldGroup#General'},
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Translations}', Target: 'texts/@UI.LineItem'},
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Admin}', Target: '@UI.FieldGroup#Admin'},
|
||||||
|
],
|
||||||
|
FieldGroup#General: {
|
||||||
|
Data: [
|
||||||
|
{Value: title},
|
||||||
|
{Value: author_ID},
|
||||||
|
{Value: genre_ID},
|
||||||
|
{Value: descr},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
FieldGroup#Details: {
|
||||||
|
Data: [
|
||||||
|
{Value: stock},
|
||||||
|
{Value: price},
|
||||||
|
{Value: currency_code, Label: '{i18n>Currency}'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
FieldGroup#Admin: {
|
||||||
|
Data: [
|
||||||
|
{Value: createdBy},
|
||||||
|
{Value: createdAt},
|
||||||
|
{Value: modifiedBy},
|
||||||
|
{Value: modifiedAt}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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}'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Draft for Localized Data
|
||||||
|
//
|
||||||
|
|
||||||
|
annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
|
||||||
|
annotate AdminService.Books with @odata.draft.enabled;
|
||||||
|
|
||||||
|
annotate AdminService.Books_texts with @(
|
||||||
|
UI: {
|
||||||
|
Identification: [{Value:title}],
|
||||||
|
SelectionFields: [ locale, title ],
|
||||||
|
LineItem: [
|
||||||
|
{Value: locale, Label: 'Locale'},
|
||||||
|
{Value: title, Label: 'Title'},
|
||||||
|
{Value: descr, Label: 'Description'},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add Value Help for Locales
|
||||||
|
annotate AdminService.Books_texts {
|
||||||
|
locale @ValueList:{entity:'Languages',type:#fixed}
|
||||||
|
}
|
||||||
|
// In addition we need to expose Languages through AdminService
|
||||||
|
using { sap } from '@sap/cds/common';
|
||||||
|
extend service AdminService {
|
||||||
|
entity Languages as projection on sap.common.Languages;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
|
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
|
||||||
"use strict";
|
"use strict";
|
||||||
return AppComponent.extend("books.Component", {
|
return AppComponent.extend("admin.Component", {
|
||||||
metadata: { manifest: "json" }
|
metadata: { manifest: "json" }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
# __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459
|
# __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459
|
||||||
|
|
||||||
# JCI app descriptor contains lower case TITLE
|
# JCI app descriptor contains lower case TITLE
|
||||||
appTitle=Manage Books
|
appTitle=Bookshop Sample
|
||||||
|
|
||||||
# JCI app descriptor contains lower case DESCRIPTION
|
# JCI app descriptor contains lower case DESCRIPTION
|
||||||
appSubTitle=CAP Sample Application
|
appSubTitle=CAP Sample Application
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"_version": "1.8.0",
|
"_version": "1.8.0",
|
||||||
"sap.app": {
|
"sap.app": {
|
||||||
"id": "books",
|
"id": "admin",
|
||||||
"type": "application",
|
"type": "application",
|
||||||
"title": "Manage Books",
|
"title": "Manage Books",
|
||||||
"description": "Sample Application",
|
"description": "Sample Application",
|
||||||
"i18n": "i18n/i18n.properties",
|
"i18n": "i18n/i18n.properties",
|
||||||
"dataSources": {
|
"dataSources": {
|
||||||
"AdminService": {
|
"AdminService": {
|
||||||
"uri": "admin/",
|
"uri": "/admin/",
|
||||||
"type": "OData",
|
"type": "OData",
|
||||||
"settings": {
|
"settings": {
|
||||||
"odataVersion": "4.0"
|
"odataVersion": "4.0"
|
||||||
@@ -19,22 +19,6 @@
|
|||||||
"id": "ui5template.basicSAPUI5ApplicationProject",
|
"id": "ui5template.basicSAPUI5ApplicationProject",
|
||||||
"-id": "ui5template.smartTemplate",
|
"-id": "ui5template.smartTemplate",
|
||||||
"-version": "1.40.12"
|
"-version": "1.40.12"
|
||||||
},
|
|
||||||
"crossNavigation": {
|
|
||||||
"inbounds": {
|
|
||||||
"intent1": {
|
|
||||||
"signature": {
|
|
||||||
"parameters": {},
|
|
||||||
"additionalParameters": "allowed"
|
|
||||||
},
|
|
||||||
"semanticObject": "Books",
|
|
||||||
"action": "manage",
|
|
||||||
"title": "{{appTitle}}",
|
|
||||||
"info": "{{appInfo}}",
|
|
||||||
"subTitle": "{{appSubTitle}}",
|
|
||||||
"icon": "sap-icon://course-book"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sap.ui5": {
|
"sap.ui5": {
|
||||||
@@ -89,7 +73,6 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"settings" : {
|
"settings" : {
|
||||||
"entitySet" : "Books",
|
"entitySet" : "Books",
|
||||||
"initialLoad": true,
|
|
||||||
"navigation" : {
|
"navigation" : {
|
||||||
"Books" : {
|
"Books" : {
|
||||||
"detail" : {
|
"detail" : {
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
{
|
|
||||||
"services": {
|
|
||||||
"LaunchPage": {
|
|
||||||
"adapter": {
|
|
||||||
"config": {
|
|
||||||
"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",
|
|
||||||
"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-manage"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "manage",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user