Spring cleaning, part 2 (#813)
* Remove OData v2 show case: Genres TreeView * Refactored bookstore and fiori services: consolidate routing and remove deprecated files * Add Jest configuration file with silent mode enabled * Refactor test files: streamline imports and remove redundant code * Change price field type to Price for better clarity and precision * Example for separation of concerns: access-control.cds
This commit is contained in:
3
.npmrc
3
.npmrc
@@ -1,3 +0,0 @@
|
|||||||
# Ensure we always use public packages, i.e. avoid using local registries from ~/.npmrc
|
|
||||||
@sap:registry=https://registry.npmjs.org/
|
|
||||||
registry=https://registry.npmjs.org/
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
{
|
|
||||||
"$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.requires` section in `package.json` is a place to configure which of the `db/sqlite` and `db/hana` folders are used for which database.\n\nWe use [Node.js profiles](https://cap.cloud.sap/docs/node.js/cds-env#profiles) to separate the configuration.\nIn the `development` profile, you can see that `db/sqlite` is set as the model, while the `db/hana` folder is configured in the `production` profile. `db-ext` is a pseudo datasource, its name doesn't matter.\n\nSee [`cds.resolve`](https://cap.cloud.sap/docs/node.js/cds-compile#cds-resolve) to learn more about how models are found.",
|
|
||||||
"selection": {
|
|
||||||
"start": {
|
|
||||||
"line": 41,
|
|
||||||
"character": 1
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 48,
|
|
||||||
"character": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Configuration"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file": "fiori/package.json",
|
|
||||||
"description": "#### Run with SQLite\n\nTo run with `development` and an in-memory SQLite database, you don't need to do anything special, because it's activated by default. Just run:\n\n>> cds watch fiori\n\nThen open [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) to see the two new fields.\n",
|
|
||||||
"line": 43,
|
|
||||||
"title": "Run with SQLite"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file": "fiori/package.json",
|
|
||||||
"description": "#### Deploy the CDS Model to SAP HANA\n\nTo 'activate' SAP HANA through the `production` profile, you can use the global `--production` flag:\n\n>> cd fiori; cds deploy --to hana --production\n\n[Learn more about SAP HANA deployment](https://cap.cloud.sap/docs/guides/databases#get-hana)\n\n#### Run the Application\n\n>> cd fiori; cds watch --production\n\nThe service on [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) is the same as before, but this time the `Authors` entity is backed by a database view with an SAP HANA function.\n\n#### More\n\nIf you don't see data, you can add some in the next step.",
|
|
||||||
"line": 46,
|
|
||||||
"title": "Run with SAP HANA"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file": "fiori/test/requests.http",
|
|
||||||
"description": "### Add More Data\n\nOptionally you can add some `Authors` data by clicking on the _Send Request_ link (provided by the [REST client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension).",
|
|
||||||
"line": 72,
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
{
|
|
||||||
"$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/srv/world.cds",
|
|
||||||
"description": "### Hello World!\n\nThis is a simplistic [Hello World](https://cap.cloud.sap/docs/get-started/hello-world) service using [CDS](https://cap.cloud.sap/docs/cds/) and [cds.services](https://cap.cloud.sap/docs/node.js/api#services-api).",
|
|
||||||
"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": "### Orders - Compositions and Serving Documents\n\nA standalone orders management service, demonstrating:\n- Using [Compositions](https://cap.cloud.sap/docs/cds/cdl#compositions) in [Domain Models](https://cap.cloud.sap/docs/guides/domain-models), along with\n- [Serving deeply nested documents](https://cap.cloud.sap/docs/guides/generic-providers#serving-structured-data)\n",
|
|
||||||
"line": 1,
|
|
||||||
"selection": {
|
|
||||||
"start": {
|
|
||||||
"line": 1,
|
|
||||||
"character": 1
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 27,
|
|
||||||
"character": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Orders"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file": "reviews/db/schema.cds",
|
|
||||||
"description": "### Reviews - More Modularity\n\nShows how to implement a modular service to manage product reviews, including:\n- Consuming other services synchronously and asynchronously\n- Serving requests synchronously\n- Emitting events asynchronously\n- Grow as you go, with:\n- Mocking app services\n- Running service meshes\n- Late-cut Micro Services\n- As well as managed data, input validations, and authorization\n",
|
|
||||||
"line": 1,
|
|
||||||
"selection": {
|
|
||||||
"start": {
|
|
||||||
"line": 1,
|
|
||||||
"character": 1
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 39,
|
|
||||||
"character": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Reviews"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Bookstore",
|
|
||||||
"description": "### Bookstore - Reuse and UI\n\n- A [composite app, reusing and combining](https://cap.cloud.sap/docs/guides/reuse-and-compose) these packages:\n - [@capire/bookshop](bookshop)\n - [@capire/reviews](reviews)\n - [@capire/orders](orders)\n - [@capire/common](common)\n- [The Vue.js app](bookshop/app/vue) imported from bookshop is served as well\n- [The Vue.js app](reviews/app/vue) imported from reviews is served as well\n- [The Fiori app](orders/app) imported from orders is served as well\n- [OpenAPI export + Swagger UI](https://cap.cloud.sap/docs/advanced/openapi)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file": "fiori/app/services.cds",
|
|
||||||
"description": "### Annotations for SAP Fiori Elements\n\nAdds an SAP Fiori elements application to bookstore, thereby introducing:\n- OData Annotations in `.cds` files\n- Support for Fiori Draft\n- Support for Value Helps\n- Serving SAP Fiori apps locally\n\nSee the [Serving Fiori UIs](https://cap.cloud.sap/docs/advanced/fiori) documentation for more information.",
|
|
||||||
"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",
|
|
||||||
"selection": {
|
|
||||||
"start": {
|
|
||||||
"line": 8,
|
|
||||||
"character": 1
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 16,
|
|
||||||
"character": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": "Packages"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isPrimary": true,
|
|
||||||
"description": "Overview of CAP Samples for Node.js"
|
|
||||||
}
|
|
||||||
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@@ -9,8 +9,6 @@
|
|||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"mechatroner.rainbow-csv",
|
"mechatroner.rainbow-csv",
|
||||||
"humao.rest-client",
|
"humao.rest-client",
|
||||||
"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": [
|
||||||
|
|||||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -1,9 +1,8 @@
|
|||||||
{
|
{
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
".reuse/**": true,
|
"**/node_modules": true,
|
||||||
"**/.gitignore": true,
|
"LICENSES": true,
|
||||||
"**/.vscode": true,
|
".reuse": true
|
||||||
"LICENSES/**": true
|
|
||||||
},
|
},
|
||||||
"debug.javascript.terminalOptions": {
|
"debug.javascript.terminalOptions": {
|
||||||
"skipFiles": [
|
"skipFiles": [
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ entity Books : managed {
|
|||||||
author : Association to Authors @mandatory;
|
author : Association to Authors @mandatory;
|
||||||
genre : Association to Genres;
|
genre : Association to Genres;
|
||||||
stock : Integer;
|
stock : Integer;
|
||||||
price : Decimal;
|
price : Price;
|
||||||
currency : Currency;
|
currency : Currency;
|
||||||
image : LargeBinary @Core.MediaType: 'image/png';
|
image : LargeBinary @Core.MediaType: 'image/png';
|
||||||
}
|
}
|
||||||
@@ -29,3 +29,5 @@ entity Genres : sap.common.CodeList {
|
|||||||
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);
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
"app",
|
"app",
|
||||||
"srv",
|
"srv",
|
||||||
"db",
|
"db",
|
||||||
"index.cds",
|
"index.cds"
|
||||||
"index.js"
|
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cap-js/sqlite": "*"
|
"@cap-js/sqlite": "*"
|
||||||
|
|||||||
2
bookshop/srv/access-control.cds
Normal file
2
bookshop/srv/access-control.cds
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
using { AdminService } from './admin-service';
|
||||||
|
annotate AdminService with @requires:'admin';
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using { sap.capire.bookshop as my } from '../db/schema';
|
using { sap.capire.bookshop as my } from '../db/schema';
|
||||||
service AdminService @(requires:'admin', path:'/admin') {
|
service AdminService @(path:'/admin') {
|
||||||
entity Books as projection on my.Books;
|
|
||||||
entity Authors as projection on my.Authors;
|
entity Authors as projection on my.Authors;
|
||||||
|
entity Books as projection on my.Books;
|
||||||
|
entity Genres as projection on my.Genres;
|
||||||
}
|
}
|
||||||
|
|||||||
7
bookstore/app/routes.js
Normal file
7
bookstore/app/routes.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Add routes to UIs from imported packages
|
||||||
|
module.exports = (app) => {
|
||||||
|
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
|
||||||
|
app.serve ('/reviews') .from ('@capire/reviews','app/vue')
|
||||||
|
app.serve ('/orders') .from('@capire/orders','app/orders')
|
||||||
|
app.serve ('/data') .from('@capire/data-viewer','app/viewer')
|
||||||
|
}
|
||||||
1
bookstore/cds-plugin.js
Normal file
1
bookstore/cds-plugin.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
require('./srv/server')
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
const cds = require ('@sap/cds')
|
|
||||||
|
|
||||||
// Add mashup logic
|
|
||||||
cds.once('served', require('./srv/mashup'))
|
|
||||||
|
|
||||||
// Add routes to UIs from imported packages
|
|
||||||
cds.once('bootstrap',(app)=>{
|
|
||||||
try {
|
|
||||||
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
|
|
||||||
app.serve ('/reviews') .from ('@capire/reviews','app/vue')
|
|
||||||
app.serve ('/orders') .from('@capire/orders','app/orders')
|
|
||||||
app.serve ('/data') .from('@capire/data-viewer','app/viewer')
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code === 'MODULE_NOT_FOUND') throw new Error('Run "npm ci" to install the required dependencies', { cause: err })
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add Swagger UI
|
|
||||||
require('./srv/swagger-ui')
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
////////////////////////////////////////////////////////////////////////////
|
const cds = require ('@sap/cds')
|
||||||
//
|
|
||||||
// Mashing up bookshop services with required services...
|
// Add routes to UIs from imported packages
|
||||||
//
|
cds.once ('bootstrap', require('../app/routes'))
|
||||||
module.exports = async()=>{ // called by server.js
|
|
||||||
|
// Mashing up bookshop services with required services...
|
||||||
|
cds.once ('served', async ()=>{ // called by server.js
|
||||||
|
|
||||||
const cds = require('@sap/cds')
|
|
||||||
const CatalogService = await cds.connect.to ('CatalogService')
|
const CatalogService = await cds.connect.to ('CatalogService')
|
||||||
const ReviewsService = await cds.connect.to ('ReviewsService')
|
const ReviewsService = await cds.connect.to ('ReviewsService')
|
||||||
const OrdersService = await cds.connect.to ('OrdersService')
|
const OrdersService = await cds.connect.to ('OrdersService')
|
||||||
@@ -55,4 +56,4 @@ module.exports = async()=>{ // called by server.js
|
|||||||
.and ('stock >=', deltaQuantity)
|
.and ('stock >=', deltaQuantity)
|
||||||
.set ('stock -=', deltaQuantity)
|
.set ('stock -=', deltaQuantity)
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Adding Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
|
|
||||||
const cds = require ('@sap/cds')
|
|
||||||
try {
|
|
||||||
const cds_swagger = require ('cds-swagger-ui-express')
|
|
||||||
cds.once ('bootstrap', app => app.use (cds_swagger()) )
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code !== 'MODULE_NOT_FOUND') throw err
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
"LaunchPage": {
|
"LaunchPage": {
|
||||||
"adapter": {
|
"adapter": {
|
||||||
"config": {
|
"config": {
|
||||||
"catalogs": [],
|
|
||||||
"groups": [
|
"groups": [
|
||||||
{
|
{
|
||||||
"id": "Bookshop",
|
"id": "Bookshop",
|
||||||
@@ -19,14 +18,6 @@
|
|||||||
"title": "Browse Books",
|
"title": "Browse Books",
|
||||||
"targetURL": "#Books-display"
|
"targetURL": "#Books-display"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "BrowseGenres",
|
|
||||||
"tileType": "sap.ushell.ui.tile.StaticTile",
|
|
||||||
"properties": {
|
|
||||||
"title": "Browse Genres (OData v2)",
|
|
||||||
"targetURL": "#Genres-display"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -115,24 +106,6 @@
|
|||||||
"url": "/admin-authors/webapp"
|
"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": {
|
"ManageBooks": {
|
||||||
"semanticObject": "Books",
|
"semanticObject": "Books",
|
||||||
"action": "manage",
|
"action": "manage",
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
using { sap.capire.bookshop } from '../../db/common';
|
|
||||||
|
|
||||||
annotate bookshop.GenreHierarchy {
|
|
||||||
ID @sap.hierarchy.node.for;
|
|
||||||
parent @sap.hierarchy.parent.node.for;
|
|
||||||
hierarchyLevel @sap.hierarchy.level.for;
|
|
||||||
drillState @sap.hierarchy.drill.state.for;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
sap.ui.define(["sap/suite/ui/generic/template/lib/AppComponent"], (AppComponent) =>
|
|
||||||
AppComponent.extend("genres.Component", {
|
|
||||||
metadata: {
|
|
||||||
manifest: "json",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#XTIT
|
|
||||||
appTitle=Genres
|
|
||||||
#XTXT
|
|
||||||
appDescription=Browse Genres
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
{
|
|
||||||
"_version": "1.8.0",
|
|
||||||
"sap.app": {
|
|
||||||
"id": "genres",
|
|
||||||
"type": "application",
|
|
||||||
"i18n": "i18n/i18n.properties",
|
|
||||||
"applicationVersion": {
|
|
||||||
"version": "1.0.0"
|
|
||||||
},
|
|
||||||
"title": "Browse Genres Hierarchy (OData v2)",
|
|
||||||
"description": "{{appDescription}}",
|
|
||||||
"tags": {
|
|
||||||
"keywords": []
|
|
||||||
},
|
|
||||||
"crossNavigation": {
|
|
||||||
"inbounds": {
|
|
||||||
"appShow": {
|
|
||||||
"title": "{{appTitle}}",
|
|
||||||
"semanticObject": "GenreHierarchy",
|
|
||||||
"action": "display",
|
|
||||||
"deviceTypes": {
|
|
||||||
"desktop": true,
|
|
||||||
"tablet": true,
|
|
||||||
"phone": true
|
|
||||||
},
|
|
||||||
"icon": "sap-icon://settings",
|
|
||||||
"size": "1x1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"outbounds": {}
|
|
||||||
},
|
|
||||||
"ach": "",
|
|
||||||
"resources": "resources.json",
|
|
||||||
"dataSources": {
|
|
||||||
"main": {
|
|
||||||
"uri": "/odata/v2/browse",
|
|
||||||
"type": "OData",
|
|
||||||
"settings": {
|
|
||||||
"annotations": ["localAnnotations"],
|
|
||||||
"localUri": "localService/metadata.xml"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"localAnnotations": {
|
|
||||||
"type": "ODataAnnotation",
|
|
||||||
"uri": "annotations/localAnnotations.xml",
|
|
||||||
"settings": {
|
|
||||||
"localUri": "annotations/localAnnotations.xml"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"offline": false,
|
|
||||||
"sourceTemplate": {
|
|
||||||
"id": "ui5template.smartTemplate",
|
|
||||||
"version": "1.40.12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sap.ui": {
|
|
||||||
"technology": "UI5",
|
|
||||||
"icons": {
|
|
||||||
"icon": "",
|
|
||||||
"favIcon": "",
|
|
||||||
"phone": "",
|
|
||||||
"phone@2": "",
|
|
||||||
"tablet": "",
|
|
||||||
"tablet@2": ""
|
|
||||||
},
|
|
||||||
"deviceTypes": {
|
|
||||||
"desktop": true,
|
|
||||||
"tablet": true,
|
|
||||||
"phone": true
|
|
||||||
},
|
|
||||||
"supportedThemes": ["sap_hcb", "sap_belize", "sap_belize_deep", "sap_fiori_3"]
|
|
||||||
},
|
|
||||||
"sap.ui5": {
|
|
||||||
"resources": {
|
|
||||||
"js": [],
|
|
||||||
"css": []
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"minUI5Version": "1.65.6",
|
|
||||||
"libs": {},
|
|
||||||
"components": {}
|
|
||||||
},
|
|
||||||
"models": {
|
|
||||||
"i18n": {
|
|
||||||
"type": "sap.ui.model.resource.ResourceModel",
|
|
||||||
"uri": "i18n/i18n.properties"
|
|
||||||
},
|
|
||||||
"@i18n": {
|
|
||||||
"type": "sap.ui.model.resource.ResourceModel",
|
|
||||||
"uri": "i18n/i18n.properties"
|
|
||||||
},
|
|
||||||
"json": {
|
|
||||||
"type": "sap.ui.model.json.JSONModel"
|
|
||||||
},
|
|
||||||
"i18n|sap.suite.ui.generic.template.ListReport|Genres": {
|
|
||||||
"type": "sap.ui.model.resource.ResourceModel",
|
|
||||||
"uri": "i18n/ListReport/Genres/i18n.properties"
|
|
||||||
},
|
|
||||||
"": {
|
|
||||||
"dataSource": "main",
|
|
||||||
"preload": true,
|
|
||||||
"settings": {
|
|
||||||
"useBatch": true,
|
|
||||||
"defaultBindingMode": "TwoWay",
|
|
||||||
"defaultCountMode": "Inline",
|
|
||||||
"refreshAfterChange": true,
|
|
||||||
"metadataUrlParams": {
|
|
||||||
"sap-value-list": "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"contentDensities": {
|
|
||||||
"compact": true,
|
|
||||||
"cozy": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sap.ui.generic.app": {
|
|
||||||
"_version": "1.3.0",
|
|
||||||
"settings": {
|
|
||||||
"forceGlobalRefresh": false,
|
|
||||||
"useColumnLayoutForSmartForm": false,
|
|
||||||
"showBasicSearch": false
|
|
||||||
},
|
|
||||||
"pages": {
|
|
||||||
"ListReport|Genres": {
|
|
||||||
"entitySet": "GenreHierarchy",
|
|
||||||
"component": {
|
|
||||||
"name": "sap.suite.ui.generic.template.ListReport",
|
|
||||||
"list": true,
|
|
||||||
"settings": {
|
|
||||||
"condensedTableLayout": true,
|
|
||||||
"smartVariantManagement": true,
|
|
||||||
"tableType": "TreeTable",
|
|
||||||
"enableTableFilterInPageVariant": true,
|
|
||||||
"dataLoadSettings": {
|
|
||||||
"loadDataOnAppLaunch": "always"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sap.fiori": {
|
|
||||||
"registrationIds": [],
|
|
||||||
"archeType": "transactional"
|
|
||||||
},
|
|
||||||
"sap.platform.hcp": {
|
|
||||||
"uri": ""
|
|
||||||
},
|
|
||||||
"sap.platform.cf": {
|
|
||||||
"oAuthScopes": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,6 @@
|
|||||||
using from './admin-authors/fiori-service';
|
using from './admin-authors/fiori-service';
|
||||||
using from './admin-books/fiori-service';
|
using from './admin-books/fiori-service';
|
||||||
using from './browse/fiori-service';
|
using from './browse/fiori-service';
|
||||||
using from './genres/fiori-service';
|
|
||||||
using from './common';
|
using from './common';
|
||||||
using from '@capire/bookstore/srv/mashup';
|
using from '@capire/bookstore/srv/mashup';
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capire/bookstore": "*",
|
"@capire/bookstore": "*",
|
||||||
"@sap/cds": ">=5",
|
"@sap/cds": ">=5",
|
||||||
"@cap-js-community/odata-v2-adapter": "^1",
|
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -24,20 +23,8 @@
|
|||||||
"kind": "odata",
|
"kind": "odata",
|
||||||
"model": "@capire/orders"
|
"model": "@capire/orders"
|
||||||
},
|
},
|
||||||
"messaging": {
|
"messaging": true,
|
||||||
"[production]": {
|
"db": true,
|
||||||
"kind": "enterprise-messaging"
|
|
||||||
},
|
|
||||||
"[development]": {
|
|
||||||
"kind": "file-based-messaging"
|
|
||||||
},
|
|
||||||
"[hybrid]": {
|
|
||||||
"kind": "enterprise-messaging-shared"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"db": {
|
|
||||||
"kind": "sql"
|
|
||||||
},
|
|
||||||
"db-ext": {
|
"db-ext": {
|
||||||
"[development]": {
|
"[development]": {
|
||||||
"model": "db/sqlite"
|
"model": "db/sqlite"
|
||||||
@@ -46,9 +33,6 @@
|
|||||||
"model": "db/hana"
|
"model": "db/hana"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"hana": {
|
|
||||||
"deploy-format": "hdbtable"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sapux": [
|
"sapux": [
|
||||||
|
|||||||
3
jest.config.mjs
Normal file
3
jest.config.mjs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
silent: true
|
||||||
|
}
|
||||||
1494
package-lock.json
generated
1494
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@@ -4,9 +4,6 @@
|
|||||||
"description": "A monorepo with several samples for CAP.",
|
"description": "A monorepo with several samples for CAP.",
|
||||||
"repository": "https://github.com/sap-samples/cloud-cap-samples.git",
|
"repository": "https://github.com/sap-samples/cloud-cap-samples.git",
|
||||||
"author": "daniel.hutzel@sap.com",
|
"author": "daniel.hutzel@sap.com",
|
||||||
"dependencies": {
|
|
||||||
"@sap/cds": ">=8"
|
|
||||||
},
|
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"./bookshop",
|
"./bookshop",
|
||||||
"./bookstore",
|
"./bookstore",
|
||||||
@@ -19,21 +16,19 @@
|
|||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cap-js/cds-test": "^0",
|
"@cap-js/cds-test": "^0",
|
||||||
"@cap-js/cds-types": "^0",
|
"@eslint/js": "^9.22.0"
|
||||||
"@cap-js/sqlite": "^1",
|
|
||||||
"eslint": "^9",
|
|
||||||
"semver": "^7"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "mbt build -t gen --mtar mta.tar",
|
"bookstore": "cds watch bookstore",
|
||||||
"deploy": "cf deploy gen/mta.tar",
|
|
||||||
"undeploy": "cf undeploy capire.samples --delete-services --delete-service-keys",
|
|
||||||
"bookshop": "cds watch bookshop",
|
"bookshop": "cds watch bookshop",
|
||||||
"fiori": "cds watch fiori",
|
"fiori": "cds watch fiori",
|
||||||
"lint": "eslint",
|
"orders": "cds watch orders",
|
||||||
"test": "npx jest --silent",
|
"reviews": "cds watch reviews",
|
||||||
"jest": "npx jest --silent",
|
"lint": "npx eslint",
|
||||||
"mocha": "CDS_TEST_SILENT=y npx mocha"
|
"test": "chest test",
|
||||||
|
"build": "mbt build -t gen --mtar mta.tar",
|
||||||
|
"deploy": "cf deploy gen/mta.tar",
|
||||||
|
"undeploy": "cf undeploy capire.samples --delete-services --delete-service-keys"
|
||||||
},
|
},
|
||||||
"mocha": {
|
"mocha": {
|
||||||
"recursive": true,
|
"recursive": true,
|
||||||
@@ -42,4 +37,4 @@
|
|||||||
},
|
},
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
const cds = require('@sap/cds')
|
const cds = require('@sap/cds')
|
||||||
|
const { expect } = cds.test ('@capire/bookshop')
|
||||||
|
|
||||||
describe('cap/samples - Consuming Services locally', () => {
|
describe('cap/samples - Consuming Services locally', () => {
|
||||||
|
|
||||||
const { expect } = cds.test ('@capire/bookshop')
|
|
||||||
|
|
||||||
it('bootstrapped the database successfully', ()=>{
|
it('bootstrapped the database successfully', ()=>{
|
||||||
const { AdminService } = cds.services
|
const { AdminService } = cds.services
|
||||||
const { Authors } = AdminService.entities
|
const { Authors } = AdminService.entities
|
||||||
@@ -33,33 +32,11 @@ describe('cap/samples - Consuming Services locally', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}).where(`name like`, 'E%')
|
}).where(`name like`, 'E%')
|
||||||
if (require('semver').gte(cds.version, '5.9.0')) {
|
|
||||||
expect(authors).to.containSubset([
|
|
||||||
{
|
|
||||||
name: 'Emily Brontë',
|
|
||||||
books: [
|
|
||||||
{
|
|
||||||
title: 'Wuthering Heights',
|
|
||||||
currency: { name: 'British Pound', symbol: '£' },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Edgar Allen Poe',
|
|
||||||
books: [
|
|
||||||
{ title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } },
|
|
||||||
{ title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
expect(authors).to.containSubset([
|
expect(authors).to.containSubset([
|
||||||
{
|
{
|
||||||
name: 'Emily Brontë',
|
name: 'Emily Brontë',
|
||||||
books: [
|
books: [
|
||||||
{
|
{
|
||||||
ID: 201,
|
|
||||||
title: 'Wuthering Heights',
|
title: 'Wuthering Heights',
|
||||||
currency: { name: 'British Pound', symbol: '£' },
|
currency: { name: 'British Pound', symbol: '£' },
|
||||||
},
|
},
|
||||||
@@ -68,8 +45,8 @@ describe('cap/samples - Consuming Services locally', () => {
|
|||||||
{
|
{
|
||||||
name: 'Edgar Allen Poe',
|
name: 'Edgar Allen Poe',
|
||||||
books: [
|
books: [
|
||||||
{ ID: 251, title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } },
|
{ title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } },
|
||||||
{ ID: 252, title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } },
|
{ title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
const cds = require('@sap/cds')
|
const cds = require('@sap/cds')
|
||||||
|
const { GET, POST, expect } = cds.test(__dirname+'/../bookshop')
|
||||||
|
cds.User.default = cds.User.Privileged // hard core monkey patch
|
||||||
|
|
||||||
describe('cap/samples - Custom Handlers', () => {
|
describe('cap/samples - Custom Handlers', () => {
|
||||||
|
|
||||||
const { GET, POST, expect } = cds.test(__dirname+'/../bookshop')
|
|
||||||
beforeAll(()=>{
|
|
||||||
cds.User.default = cds.User.Privileged // hard core monkey patch
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should reject out-of-stock orders', async () => {
|
it('should reject out-of-stock orders', async () => {
|
||||||
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled
|
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled
|
||||||
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled
|
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
// Quick hack: suppress deprecation warnings w/ Node22 caused by http-proxy (used by OData v2 proxy)
|
|
||||||
// See also: https://github.com/http-party/node-http-proxy/pull/1666
|
|
||||||
require('util')._extend = Object.assign
|
|
||||||
|
|
||||||
const cds = require('@sap/cds')
|
|
||||||
|
|
||||||
describe('cap/samples - Fiori APIs - v2', function() {
|
|
||||||
|
|
||||||
const { GET, expect, axios } = cds.test ('@capire/fiori', '--with-mocks')
|
|
||||||
axios.defaults.auth = { username: 'alice', password: 'admin' }
|
|
||||||
|
|
||||||
// if (this.timeout) this.timeout(1e6)
|
|
||||||
|
|
||||||
it('serves $metadata documents in v2', async () => {
|
|
||||||
const { headers, data } = await GET `/odata/v2/browse/$metadata`
|
|
||||||
expect(headers).to.contain({
|
|
||||||
'content-type': 'application/xml',
|
|
||||||
'dataserviceversion': '2.0',
|
|
||||||
})
|
|
||||||
expect(data).to.contain('<EntitySet Name="GenreHierarchy" EntityType="CatalogService.GenreHierarchy"/>')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('serves Books in v2', async () => {
|
|
||||||
const { data } = await GET `/odata/v2/browse/Books`
|
|
||||||
expect(data).to.containSubset({d:{results:[]}})
|
|
||||||
expect(data.d.results.length).to.be.greaterThanOrEqual(5)
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
@@ -1,13 +1,9 @@
|
|||||||
const cds = require('@sap/cds')
|
const cds = require('@sap/cds')
|
||||||
|
const { GET, expect } = cds.test (__dirname)
|
||||||
|
cds.User.default = cds.User.Privileged // hard core monkey patch
|
||||||
|
|
||||||
describe('cap/samples - Localized Data', () => {
|
describe('cap/samples - Localized Data', () => {
|
||||||
|
|
||||||
const { GET, expect } = cds.test (__dirname)
|
|
||||||
beforeAll(()=>{
|
|
||||||
cds.User.default = cds.User.Privileged // hard core monkey patch
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
it('serves localized $metadata documents', async () => {
|
it('serves localized $metadata documents', async () => {
|
||||||
const { data } = await GET(`/browse/$metadata?sap-language=de`, { headers: { 'accept-language': 'de' }})
|
const { data } = await GET(`/browse/$metadata?sap-language=de`, { headers: { 'accept-language': 'de' }})
|
||||||
expect(data).to.contain('<Annotation Term="Common.Label" String="Währung"/>')
|
expect(data).to.contain('<Annotation Term="Common.Label" String="Währung"/>')
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const cds = require('@sap/cds')
|
const cds = require('@sap/cds')
|
||||||
|
const { expect } = cds.test.in(__dirname,'..')
|
||||||
|
|
||||||
describe('cap/samples - Messaging', ()=>{
|
describe('cap/samples - Messaging', ()=>{
|
||||||
|
|
||||||
const { expect } = cds.test.in(__dirname,'..')
|
|
||||||
const _model = '@capire/reviews'
|
const _model = '@capire/reviews'
|
||||||
const Reviews = 'sap.capire.reviews.Reviews'
|
const Reviews = 'sap.capire.reviews.Reviews'
|
||||||
beforeAll(()=>{
|
beforeAll(()=>{
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const cds = require('@sap/cds')
|
const cds = require('@sap/cds')
|
||||||
|
const { GET, expect, axios } = cds.test ('@capire/bookshop')
|
||||||
|
axios.defaults.auth = { username: 'alice', password: 'admin' }
|
||||||
|
|
||||||
describe('cap/samples - Bookshop APIs', () => {
|
describe('cap/samples - Bookshop APIs', () => {
|
||||||
const { GET, expect, axios } = cds.test ('@capire/bookshop')
|
|
||||||
axios.defaults.auth = { username: 'alice', password: 'admin' }
|
|
||||||
|
|
||||||
it('serves $metadata documents in v4', async () => {
|
it('serves $metadata documents in v4', async () => {
|
||||||
const { headers, status, data } = await GET `/browse/$metadata`
|
const { headers, status, data } = await GET `/browse/$metadata`
|
||||||
|
|||||||
Reference in New Issue
Block a user