Compare commits
2 Commits
adding-que
...
preview
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9af7b242d | ||
|
|
8a55c2e68b |
1
.env
1
.env
@@ -1 +1,2 @@
|
|||||||
cds.features.snapi = y
|
cds.features.snapi = y
|
||||||
|
cds.cdsc.beta.aspectCompositions = true
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
name: Question, feedback or bug?
|
|
||||||
about: Use our community!
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Please use our community on https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce
|
|
||||||
28
.github/workflows/node.js.yml
vendored
28
.github/workflows/node.js.yml
vendored
@@ -1,28 +0,0 @@
|
|||||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
|
||||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
|
||||||
|
|
||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ master ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [10.x, 12.16.2]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- run: npm install
|
|
||||||
- run: npm test
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
parallel: true
|
|
||||||
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
// 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": [
|
||||||
"SAPSE.vscode-cds",
|
// "CDS Editor !",
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"mechatroner.rainbow-csv",
|
"mechatroner.rainbow-csv",
|
||||||
|
|||||||
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
@@ -6,16 +6,24 @@
|
|||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "bookshop",
|
"name": "bookshop",
|
||||||
"command": "cds watch bookshop",
|
"cwd": "${workspaceFolder}/bookshop",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "node-terminal",
|
"type": "node",
|
||||||
|
"runtimeExecutable": "npx",
|
||||||
|
"runtimeArgs": ["-n"],
|
||||||
|
"args": ["--", "cds", "run", "--with-mocks", "--in-memory?"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
"skipFiles": ["<node_internals>/**"]
|
"skipFiles": ["<node_internals>/**"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Fiori app",
|
"name": "Fiori App",
|
||||||
"command": "cds watch fiori",
|
"cwd": "${workspaceFolder}/fiori",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "node-terminal",
|
"type": "node",
|
||||||
|
"runtimeExecutable": "npx",
|
||||||
|
"runtimeArgs": ["-n"],
|
||||||
|
"args": ["--", "cds", "run", "--with-mocks", "--in-memory?"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
"skipFiles": ["<node_internals>/**"]
|
"skipFiles": ["<node_internals>/**"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
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)
|
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)
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Preliminaries
|
### Preliminaries
|
||||||
|
|
||||||
@@ -40,19 +39,19 @@ After that open this link in your browser: [http://localhost:4004](http://localh
|
|||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
Run the provided tests with [_jest_](http://jestjs.io) or [_mocha_](http://mochajs.org), for example:
|
Run the provided tests with [_jest_](http://jestjs.io) or [_mocha_](http://mochajs.org), e.g.:
|
||||||
```sh
|
```sh
|
||||||
npx jest
|
npx jest
|
||||||
```
|
```
|
||||||
> While mocha is a bit smaller and faster, jest runs tests in parallel and isolation, which allows to run all tests.
|
> While mocha is a bit smaller and faster, jest runs tests in parallel and isolation which allows to run all tests.
|
||||||
|
|
||||||
|
|
||||||
## Get Support
|
## Get Support
|
||||||
|
|
||||||
Check out the documentation at [https://cap.cloud.sap](https://cap.cloud.sap). <br>
|
Check out the cap docs at [https://cap.cloud.sap](https://cap.cloud.sap). <br>
|
||||||
In case you have a question, find a bug, or otherwise need support, please use our [community](https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce).
|
In case you find a bug or need support, please [open an issue in here](https://github.com/SAP-samples/cloud-cap-samples/issues/new).
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSE) file.
|
Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSE) file.
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ ID;parent_ID;name
|
|||||||
19;10;Fairy Tale
|
19;10;Fairy Tale
|
||||||
20;;Non-Fiction
|
20;;Non-Fiction
|
||||||
21;20;Biography
|
21;20;Biography
|
||||||
22;21;Autobiography
|
22;20;Autobiography
|
||||||
23;20;Essay
|
23;20;Essay
|
||||||
24;20;Speech
|
24;20;Speech
|
||||||
|
|||||||
|
@@ -2,7 +2,7 @@ code;symbol;name;descr;numcode;minor;exponent
|
|||||||
EUR;€;Euro;European Euro;978;Cent;2
|
EUR;€;Euro;European Euro;978;Cent;2
|
||||||
USD;$;US Dollar;United States Dollar;840;Cent;2
|
USD;$;US Dollar;United States Dollar;840;Cent;2
|
||||||
CAD;$;Canadian Dollar;Canadian Dollar;124;Cent;2
|
CAD;$;Canadian Dollar;Canadian Dollar;124;Cent;2
|
||||||
AUD;$;Australian Dollar;Australian Dollar;036;Cent;2
|
AUD;$;Australian Dollar;Canadian Dollar;036;Cent;2
|
||||||
GBP;£;British Pound;Great Britain Pound;826;Penny;2
|
GBP;£;British Pound;Great Britain Pound;826;Penny;2
|
||||||
ILS;₪;Shekel;Israeli New Shekel;376;Agorat;2
|
ILS;₪;Shekel;Israeli New Shekel;376;Agorat;2
|
||||||
INR;₹;Rupee;Indian Rupee;356;Paise;2
|
INR;₹;Rupee;Indian Rupee;356;Paise;2
|
||||||
|
|||||||
|
@@ -7,19 +7,7 @@ AuthorID = Author ID
|
|||||||
Stock = Stock
|
Stock = Stock
|
||||||
Name = Name
|
Name = Name
|
||||||
AuthorName = Author's Name
|
AuthorName = Author's Name
|
||||||
DateOfBirth = Date of Birth
|
|
||||||
DateOfDeath = Date of Death
|
|
||||||
PlaceOfBirth = Place of Birth
|
|
||||||
PlaceOfDeath = Place of Death
|
|
||||||
Authors = Authors
|
Authors = Authors
|
||||||
Order = Order
|
Order = Order
|
||||||
Orders = Orders
|
Orders = Orders
|
||||||
Price = Price
|
Price = Price
|
||||||
|
|
||||||
Genre = Genre
|
|
||||||
Genres = Genres
|
|
||||||
SubGenres = Sub Genres
|
|
||||||
|
|
||||||
NumCode = Numeric Code
|
|
||||||
MinorUnit = Minor Unit
|
|
||||||
Exponent = Exponent
|
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
|
sap.ui.define(["sap/fe/core/AppComponent"], ac => ac.extend("admin.Component", {
|
||||||
"use strict";
|
metadata:{ manifest:'json' }
|
||||||
return AppComponent.extend("admin.Component", {
|
}))
|
||||||
metadata: { manifest: "json" }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/* eslint no-undef:0 */
|
/* eslint no-undef:0 */
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"sap.ui5": {
|
"sap.ui5": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"libs": {
|
"libs": {
|
||||||
"sap.fe.templates": {}
|
"sap.fe": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models": {
|
"models": {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
|
sap.ui.define(["sap/fe/core/AppComponent"], ac => ac.extend("bookshop.Component", {
|
||||||
"use strict";
|
metadata:{ manifest:'json' }
|
||||||
return AppComponent.extend("bookshop.Component", {
|
}))
|
||||||
metadata: { manifest: "json" }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
/* eslint no-undef:0 */
|
/* eslint no-undef:0 */
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"sap.ui5": {
|
"sap.ui5": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"libs": {
|
"libs": {
|
||||||
"sap.fe.templates": {}
|
"sap.fe": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models": {
|
"models": {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
using { sap.capire.bookshop as my } from '@capire/bookshop';
|
using { sap.capire.bookshop as my } from '@capire/bookshop';
|
||||||
using { sap.common } from '@capire/common';
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -28,6 +28,13 @@ annotate my.Books with @(
|
|||||||
author @ValueList.entity:'Authors';
|
author @ValueList.entity:'Authors';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
annotate my.Authors with @(
|
||||||
|
UI: {
|
||||||
|
Identification: [{Value:name}],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Books Details
|
// Books Details
|
||||||
@@ -59,199 +66,15 @@ annotate my.Books with {
|
|||||||
descr @UI.MultiLineText;
|
descr @UI.MultiLineText;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Genres List
|
|
||||||
//
|
|
||||||
annotate my.Genres with @(
|
|
||||||
Common.SemanticKey: [name],
|
|
||||||
UI: {
|
|
||||||
SelectionFields: [ name ],
|
|
||||||
LineItem:[
|
|
||||||
{Value: name},
|
|
||||||
{Value: parent.name, Label: 'Main Genre'},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Genre Details
|
|
||||||
//
|
|
||||||
annotate my.Genres with @(
|
|
||||||
UI: {
|
|
||||||
Identification: [{Value:name}],
|
|
||||||
HeaderInfo: {
|
|
||||||
TypeName: '{i18n>Genre}',
|
|
||||||
TypeNamePlural: '{i18n>Genres}',
|
|
||||||
Title: {Value: name},
|
|
||||||
Description: {Value: ID}
|
|
||||||
},
|
|
||||||
Facets: [
|
|
||||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>SubGenres}', Target: 'children/@UI.LineItem'},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Genres Elements
|
|
||||||
//
|
|
||||||
annotate my.Genres with {
|
annotate my.Genres with {
|
||||||
ID @title: '{i18n>ID}';
|
|
||||||
name @title: '{i18n>Genre}';
|
name @title: '{i18n>Genre}';
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Authors List
|
|
||||||
//
|
|
||||||
annotate my.Authors with @(
|
|
||||||
Common.SemanticKey: [name],
|
|
||||||
UI: {
|
|
||||||
Identification: [{Value:name}],
|
|
||||||
SelectionFields: [ name ],
|
|
||||||
LineItem:[
|
|
||||||
{Value: ID},
|
|
||||||
{Value: name},
|
|
||||||
{Value: dateOfBirth},
|
|
||||||
{Value: dateOfDeath},
|
|
||||||
{Value: placeOfBirth},
|
|
||||||
{Value: placeOfDeath},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Author Details
|
|
||||||
//
|
|
||||||
annotate my.Authors with @(
|
|
||||||
UI: {
|
|
||||||
HeaderInfo: {
|
|
||||||
TypeName: '{i18n>Author}',
|
|
||||||
TypeNamePlural: '{i18n>Authors}',
|
|
||||||
Title: {Value: name},
|
|
||||||
Description: {Value: dateOfBirth}
|
|
||||||
},
|
|
||||||
Facets: [
|
|
||||||
{$Type: 'UI.ReferenceFacet', Target: 'books/@UI.LineItem'},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Authors Elements
|
// Authors Elements
|
||||||
//
|
//
|
||||||
annotate my.Authors with {
|
annotate my.Authors with {
|
||||||
ID @title:'{i18n>ID}' @UI.HiddenFilter;
|
ID @title:'{i18n>ID}' @UI.HiddenFilter;
|
||||||
name @title:'{i18n>Name}';
|
name @title:'{i18n>AuthorName}';
|
||||||
dateOfBirth @title:'{i18n>DateOfBirth}';
|
|
||||||
dateOfDeath @title:'{i18n>DateOfDeath}';
|
|
||||||
placeOfBirth @title:'{i18n>PlaceOfBirth}';
|
|
||||||
placeOfDeath @title:'{i18n>PlaceOfDeath}';
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Languages List
|
|
||||||
//
|
|
||||||
annotate common.Languages with @(
|
|
||||||
Common.SemanticKey: [code],
|
|
||||||
Identification: [{Value:code}],
|
|
||||||
UI: {
|
|
||||||
SelectionFields: [ name, descr ],
|
|
||||||
LineItem:[
|
|
||||||
{Value: code},
|
|
||||||
{Value: name},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Language Details
|
|
||||||
//
|
|
||||||
annotate common.Languages with @(
|
|
||||||
UI: {
|
|
||||||
HeaderInfo: {
|
|
||||||
TypeName: '{i18n>Language}',
|
|
||||||
TypeNamePlural: '{i18n>Languages}',
|
|
||||||
Title: {Value: name},
|
|
||||||
Description: {Value: descr}
|
|
||||||
},
|
|
||||||
Facets: [
|
|
||||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
|
|
||||||
],
|
|
||||||
FieldGroup#Details: {
|
|
||||||
Data: [
|
|
||||||
{Value: code},
|
|
||||||
{Value: name},
|
|
||||||
{Value: descr}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Currencies List
|
|
||||||
//
|
|
||||||
annotate common.Currencies with @(
|
|
||||||
Common.SemanticKey: [code],
|
|
||||||
Identification: [{Value:code}],
|
|
||||||
UI: {
|
|
||||||
SelectionFields: [ name, descr ],
|
|
||||||
LineItem:[
|
|
||||||
{Value: descr},
|
|
||||||
{Value: symbol},
|
|
||||||
{Value: code},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Currency Details
|
|
||||||
//
|
|
||||||
annotate common.Currencies with @(
|
|
||||||
UI: {
|
|
||||||
HeaderInfo: {
|
|
||||||
TypeName: '{i18n>Currency}',
|
|
||||||
TypeNamePlural: '{i18n>Currencies}',
|
|
||||||
Title: {Value: descr},
|
|
||||||
Description: {Value: code}
|
|
||||||
},
|
|
||||||
Facets: [
|
|
||||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
|
|
||||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Extended}', Target: '@UI.FieldGroup#Extended'},
|
|
||||||
],
|
|
||||||
FieldGroup#Details: {
|
|
||||||
Data: [
|
|
||||||
{Value: name},
|
|
||||||
{Value: symbol},
|
|
||||||
{Value: code},
|
|
||||||
{Value: descr}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
FieldGroup#Extended: {
|
|
||||||
Data: [
|
|
||||||
{Value: numcode},
|
|
||||||
{Value: minor},
|
|
||||||
{Value: exponent}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Currencies Elements
|
|
||||||
//
|
|
||||||
annotate common.Currencies with {
|
|
||||||
numcode @title:'{i18n>NumCode}';
|
|
||||||
minor @title:'{i18n>MinorUnit}';
|
|
||||||
exponent @title:'{i18n>Exponent}';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
|
sap.ui.define(["sap/fe/core/AppComponent"], ac => ac.extend("orders.Component", {
|
||||||
"use strict";
|
metadata:{ manifest:'json' }
|
||||||
return AppComponent.extend("orders.Component", {
|
}))
|
||||||
metadata: { manifest: "json" }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/* eslint no-undef:0 */
|
/* eslint no-undef:0 */
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"sap.ui5": {
|
"sap.ui5": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"libs": {
|
"libs": {
|
||||||
"sap.fe.templates": {}
|
"sap.fe": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models": {
|
"models": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
ID;amount;parent_ID;book_ID;netAmount
|
ID;amount;parent_ID;book_ID
|
||||||
58040e66-1dcd-4ffb-ab10-fdce32028b79;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;201;11.11
|
58040e66-1dcd-4ffb-ab10-fdce32028b79;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;201
|
||||||
64e718c9-ff99-47f1-8ca3-950c850777d4;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;271;15
|
64e718c9-ff99-47f1-8ca3-950c850777d4;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;271
|
||||||
e9641166-e050-4261-bfee-d1e797e6cb7f;2;64e718c9-ff99-47f1-8ca3-950c850777d4;252;28
|
e9641166-e050-4261-bfee-d1e797e6cb7f;2;64e718c9-ff99-47f1-8ca3-950c850777d4;252
|
||||||
|
@@ -4,13 +4,10 @@ namespace sap.capire.bookshop;
|
|||||||
|
|
||||||
entity Orders : cuid, managed {
|
entity Orders : cuid, managed {
|
||||||
OrderNo : String @title:'Order Number'; //> readable key
|
OrderNo : String @title:'Order Number'; //> readable key
|
||||||
Items : Composition of many OrderItems on Items.parent = $self;
|
Items : Composition of many {
|
||||||
currency : Currency;
|
key pos : Integer;
|
||||||
}
|
|
||||||
|
|
||||||
entity OrderItems : cuid {
|
|
||||||
parent : Association to Orders;
|
|
||||||
book : Association to Books;
|
book : Association to Books;
|
||||||
amount : Integer;
|
amount : Integer;
|
||||||
netAmount : Decimal(9,2);
|
};
|
||||||
|
currency : Currency;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,6 @@
|
|||||||
"sqlite3": "^4"
|
"sqlite3": "^4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bookshop": "cds watch bookshop",
|
|
||||||
"fiori": "cds watch fiori",
|
|
||||||
"mocha": "npx mocha || echo",
|
"mocha": "npx mocha || echo",
|
||||||
"jest": "npx jest --verbose",
|
"jest": "npx jest --verbose",
|
||||||
"test": "npm run jest -s"
|
"test": "npm run jest -s"
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
cds.requires.messaging.kind = file-based-messaging
|
|
||||||
@@ -1,2 +1 @@
|
|||||||
cds.requires.messaging.kind = file-based-messaging
|
|
||||||
PORT = 5005
|
PORT = 5005
|
||||||
@@ -12,12 +12,15 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"reviews-service": "cds watch",
|
"reviews-service": "cds watch",
|
||||||
"books-reviewed": "cds watch ../reviewed"
|
"bookshop": "cds watch test/bookshop"
|
||||||
},
|
},
|
||||||
"cds": {
|
"cds": {
|
||||||
"requires": {
|
"requires": {
|
||||||
"db": {
|
"db": {
|
||||||
"kind": "sql"
|
"kind": "sql"
|
||||||
|
},
|
||||||
|
"messaging": {
|
||||||
|
"kind": "file-based-messaging"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
#################################################
|
#################################################
|
||||||
#
|
#
|
||||||
# To ReviewsService
|
# To ReviewsService mocked in bookshop process
|
||||||
#
|
#
|
||||||
# move the right down:
|
|
||||||
@reviews-service = http://localhost:4004/reviews
|
|
||||||
@reviews-service = http://localhost:5005/reviews
|
|
||||||
|
|
||||||
### Get all reviews
|
GET http://localhost:4004/reviews/Reviews?
|
||||||
GET {{reviews-service}}/Reviews
|
|
||||||
|
|
||||||
### Add a new review (with random rating)
|
###
|
||||||
POST {{reviews-service}}/Reviews
|
|
||||||
|
POST http://localhost:4004/reviews/Reviews
|
||||||
Content-Type: application/json;IEEE754Compatible=true
|
Content-Type: application/json;IEEE754Compatible=true
|
||||||
|
|
||||||
{"subject":"201", "title":"boo"}
|
{"subject":"201", "title":"boo"}
|
||||||
@@ -23,18 +20,17 @@ Content-Type: application/json;IEEE754Compatible=true
|
|||||||
# (both in-process as well as separate one)
|
# (both in-process as well as separate one)
|
||||||
#
|
#
|
||||||
|
|
||||||
@bookshop = http://localhost:4004
|
|
||||||
|
|
||||||
### Request to CatalogService > delegated to ReviewsService
|
### Request to CatalogService > delegated to ReviewsService
|
||||||
GET {{bookshop}}/browse/Books(201)/reviews?
|
GET http://localhost:4004/browse/Books(201)/reviews?
|
||||||
&$select=rating,date,reviewer,title
|
&$select=rating,date,reviewer,title
|
||||||
|
|
||||||
### Alternative OData URL
|
### Alternative OData URL
|
||||||
GET {{bookshop}}/browse/Books/201/reviews?
|
GET http://localhost:4004/browse/Books/201/reviews?
|
||||||
&$select=rating,date,reviewer,title
|
&$select=rating,date,reviewer,title
|
||||||
|
|
||||||
###
|
###
|
||||||
GET {{bookshop}}/browse/Books(201)?
|
GET http://localhost:4004/browse/Books(201)?
|
||||||
&$select=ID,title,rating
|
&$select=ID,title,rating
|
||||||
&$expand=reviews
|
&$expand=reviews
|
||||||
# Note: the $expand only works in case of ReviewsService in same process
|
# Note: the $expand only works in case of ReviewsService in same process
|
||||||
13
reviews/test/5005.http
Normal file
13
reviews/test/5005.http
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#################################################
|
||||||
|
#
|
||||||
|
# To ReviewsService running as separate process
|
||||||
|
#
|
||||||
|
|
||||||
|
GET http://localhost:5005/reviews/Reviews?
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST http://localhost:5005/reviews/Reviews
|
||||||
|
Content-Type: application/json;IEEE754Compatible=true
|
||||||
|
|
||||||
|
{"subject":"201", "title":"boo"}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
"name": "@capire/bookshop-with-reviews",
|
"name": "@capire/bookshop-with-reviews",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capire/bookshop": "../bookshop",
|
"@capire/bookshop": "../../../bookshop",
|
||||||
"@capire/reviews": "../reviews",
|
"@capire/reviews": "..",
|
||||||
"@sap/cds": "^3.33.1",
|
"@sap/cds": "^3.33.1",
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1"
|
||||||
},
|
},
|
||||||
@@ -14,6 +14,9 @@
|
|||||||
},
|
},
|
||||||
"ReviewsService": {
|
"ReviewsService": {
|
||||||
"kind": "odata", "model": "@capire/reviews"
|
"kind": "odata", "model": "@capire/reviews"
|
||||||
|
},
|
||||||
|
"messaging": {
|
||||||
|
"kind": "file-based-messaging"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,13 +12,9 @@ cds.once('served', async()=>{
|
|||||||
// react on event messages from reviews service
|
// react on event messages from reviews service
|
||||||
const ReviewsService = await cds.connect.to ('ReviewsService')
|
const ReviewsService = await cds.connect.to ('ReviewsService')
|
||||||
const db = await cds.connect.to ('db')
|
const db = await cds.connect.to ('db')
|
||||||
|
|
||||||
// reflect entities required below...
|
|
||||||
const { Books } = db.entities('sap.capire.bookshop')
|
|
||||||
const { Reviews } = ReviewsService.entities
|
|
||||||
|
|
||||||
ReviewsService.on ('reviewed', (msg) => {
|
ReviewsService.on ('reviewed', (msg) => {
|
||||||
console.debug ('> received:', msg.event, msg.data)
|
console.debug ('> received:', msg.event, msg.data)
|
||||||
|
const { Books } = db.entities('sap.capire.bookshop')
|
||||||
const { subject, rating } = msg.data
|
const { subject, rating } = msg.data
|
||||||
const tx = db.tx (msg) // TODO: db.tx(msg) fully implemented?
|
const tx = db.tx (msg) // TODO: db.tx(msg) fully implemented?
|
||||||
return tx.update (Books,subject) .with ({rating})
|
return tx.update (Books,subject) .with ({rating})
|
||||||
@@ -28,17 +24,17 @@ cds.once('served', async()=>{
|
|||||||
const CatalogService = await cds.connect.to ('CatalogService')
|
const CatalogService = await cds.connect.to ('CatalogService')
|
||||||
CatalogService.impl (srv => srv.on ('READ', 'Books/reviews', (req) => {
|
CatalogService.impl (srv => srv.on ('READ', 'Books/reviews', (req) => {
|
||||||
console.debug ('> delegating to ReviewsService')
|
console.debug ('> delegating to ReviewsService')
|
||||||
const [ id ] = req.params
|
const { Reviews } = ReviewsService.entities
|
||||||
const tx = ReviewsService.tx(req)
|
const [ subject ] = req.params
|
||||||
return tx.read (Reviews) .where ({ subject: String(id) })
|
const tx = ReviewsService.tx (req)
|
||||||
.columns (req.query.SELECT.columns)
|
return tx.read (Reviews) .where({subject}) .columns (req.query.SELECT.columns)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Other bootstrapping events you could hook in to...
|
// Other bootstrapping events you could hook in to...
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
cds.on('bootstrap',(app) => {/* ... */})
|
cds.on('boostrap',(app) => {/* ... */})
|
||||||
cds.on('loaded', (model) => {/* ... */})
|
cds.on('loaded', (model) => {/* ... */})
|
||||||
cds.on('connect', (srv) => {/* ... */})
|
cds.on('connect', (srv) => {/* ... */})
|
||||||
cds.on('serving', (srv) => {/* ... */})
|
cds.on('serving', (srv) => {/* ... */})
|
||||||
@@ -261,7 +261,7 @@ describe('cds.ql → cqn', () => {
|
|||||||
// same for works distinct
|
// same for works distinct
|
||||||
})
|
})
|
||||||
|
|
||||||
test.skip('where ( ... cql | {x:y} )', () => {
|
xtest('where ( ... cql | {x:y} )', () => {
|
||||||
const args = [`foo`, "'bar'", 3]
|
const args = [`foo`, "'bar'", 3]
|
||||||
const ID = 11
|
const ID = 11
|
||||||
|
|
||||||
@@ -359,11 +359,6 @@ describe('cds.ql → cqn', () => {
|
|||||||
CQL`SELECT from Foo where x in (SELECT y from Bar)`
|
CQL`SELECT from Foo where x in (SELECT y from Bar)`
|
||||||
)
|
)
|
||||||
|
|
||||||
// using query api
|
|
||||||
expect(SELECT.from('Books').where(
|
|
||||||
`author.name in`, SELECT('name').from('Authors'))).to.eql(CQL`SELECT from Books where author.name in (SELECT name from Authors)`
|
|
||||||
)
|
|
||||||
|
|
||||||
// in classical semi joins
|
// in classical semi joins
|
||||||
expect(
|
expect(
|
||||||
SELECT('x').from(Foo) .where ( `exists`,
|
SELECT('x').from(Foo) .where ( `exists`,
|
||||||
@@ -466,38 +461,47 @@ describe('cds.ql → cqn', () => {
|
|||||||
UPDATE.with allows to pass in plain data payloads, e.g. as obtained from REST clients.
|
UPDATE.with allows to pass in plain data payloads, e.g. as obtained from REST clients.
|
||||||
In addition, UPDATE.with supports specifying expressions, either in CQL fragements
|
In addition, UPDATE.with supports specifying expressions, either in CQL fragements
|
||||||
notation or as simple expression objects.
|
notation or as simple expression objects.
|
||||||
|
|
||||||
UPDATE.data allows to pass in plain data payloads, e.g. as obtained from REST clients.
|
|
||||||
The passed in object can be modified subsequently, e.g. by adding or modifying values
|
|
||||||
before the query is finally executed.
|
|
||||||
*/
|
*/
|
||||||
test('with + data', () => {
|
test('with', () => {
|
||||||
if (cds.version < '4.1.0') return
|
expect(UPDATE(Foo).with(`foo=`, 11, `bar-=`, 22))
|
||||||
const o = {}
|
|
||||||
const q = UPDATE(Foo).data(o).with(`bar-=`, 22)
|
|
||||||
o.foo = 11
|
|
||||||
expect(q)
|
|
||||||
.to.eql(UPDATE(Foo).with(`foo=`, 11, `bar-=`, 22))
|
|
||||||
.to.eql(UPDATE(Foo).with({ foo: 11, bar: { '-=': 22 } }))
|
.to.eql(UPDATE(Foo).with({ foo: 11, bar: { '-=': 22 } }))
|
||||||
.to.eql({
|
.to.eql({
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
entity: 'Foo',
|
entity: 'Foo',
|
||||||
data: { foo: 11 },
|
|
||||||
with: {
|
with: {
|
||||||
|
foo: { val: 11 },
|
||||||
bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] },
|
bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// some more
|
// some more
|
||||||
expect(UPDATE(Foo).with(`bar = coalesce(x,y), car = 'foo''s bar, car'`)).to.eql({
|
// expect(UPDATE(Foo).with(`bar = coalesce(x,y), car = 'foo''s bar, car'`)).to.eql({
|
||||||
|
// UPDATE: {
|
||||||
|
// entity: 'Foo',
|
||||||
|
// with: {
|
||||||
|
// bar: { func: 'coalesce', args: [{ ref: ['x'] }, { ref: ['y'] }] },
|
||||||
|
// car: { val: "foo's bar, car" },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
UPDATE.data allows to pass in plain data payloads, e.g. as obtained from REST clients.
|
||||||
|
The passed in object can be modified subsequently, e.g. by adding or modifying values
|
||||||
|
before the query is finally executed.
|
||||||
|
*/
|
||||||
|
test('data', () => {
|
||||||
|
const o = {}
|
||||||
|
const q = UPDATE(Foo).data(o).with(`bar-=`, 22)
|
||||||
|
o.foo = 11
|
||||||
|
expect(q).to.eql({
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
entity: 'Foo',
|
entity: 'Foo',
|
||||||
data: {
|
data: { foo: 11 },
|
||||||
car: "foo's bar, car",
|
|
||||||
},
|
|
||||||
with: {
|
with: {
|
||||||
bar: { func: 'coalesce', args: [{ ref: ['x'] }, { ref: ['y'] }] },
|
bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
const { expect } = require('./capire')
|
const { expect } = require('./capire')
|
||||||
const cds = require('@sap/cds')
|
const cds = require('@sap/cds')
|
||||||
|
|
||||||
const cwd = process.cwd()
|
|
||||||
before (()=> process.chdir(__dirname))
|
|
||||||
after(()=> process.chdir(cwd))
|
|
||||||
|
|
||||||
describe('Consuming Services locally', () => {
|
describe('Consuming Services locally', () => {
|
||||||
//
|
//
|
||||||
before('bootstrap db and services', async () => {
|
before('bootstrap db and services', async () => {
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
const cwd = process.cwd()
|
|
||||||
const is_jest = !!global.test
|
|
||||||
if (is_jest) { // it's jest
|
|
||||||
global.before = (msg,fn) => global.beforeAll(fn||msg)
|
|
||||||
global.after = (msg,fn) => global.afterAll(fn||msg)
|
|
||||||
}
|
|
||||||
before (()=> process.chdir(__dirname))
|
|
||||||
after (()=> process.chdir(cwd))
|
|
||||||
|
|
||||||
describe('Custom Handlers', () => {
|
describe('Custom Handlers', () => {
|
||||||
const { GET, POST, expect } = require('./capire').launch('bookshop')
|
const { GET, POST, expect } = require('./capire').launch('bookshop')
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ describe('Localized Data', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
xit('supports @cds.localized:false', async ()=>{
|
it('supports @cds.localized:false', async ()=>{
|
||||||
const { data } = await GET(`/browse/BooksSans?&$select=title,localized_title&$expand=currency&$filter=locale eq 'de' or locale eq null`, {
|
const { data } = await GET(`/browse/BooksSans?&$select=title,localized_title&$expand=currency&$filter=locale eq 'de' or locale eq null`, {
|
||||||
headers: { 'Accept-Language': 'de' },
|
headers: { 'Accept-Language': 'de' },
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ describe('Messaging', ()=>{
|
|||||||
|
|
||||||
let N=0, received=[], M=0
|
let N=0, received=[], M=0
|
||||||
it ('should add messaging event handlers', ()=>{
|
it ('should add messaging event handlers', ()=>{
|
||||||
srv.on('reviewed', (msg)=> {received.push(msg)})
|
srv.on('reviewed', (msg)=> received.push(msg))
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('should add more messaging event handlers', ()=>{
|
it ('should add more messaging event handlers', ()=>{
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
describe('Querying', () => {
|
|
||||||
const { GET, expect } = require('./capire').launch('bookshop')
|
|
||||||
const Authors = {name:'sap.capire.bookshop.Authors'}
|
|
||||||
|
|
||||||
it('should SELECT from Authors', async () => {
|
|
||||||
const authors = await SELECT.from (Authors, a => {
|
|
||||||
a.name
|
|
||||||
})
|
|
||||||
expect(authors).to.eql([
|
|
||||||
{ name: 'Emily Brontë' },
|
|
||||||
{ name: 'Charlotte Brontë' },
|
|
||||||
{ name: 'Edgar Allen Poe' },
|
|
||||||
{ name: 'Richard Carpenter' },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should SELECT name from Authors', async () => {
|
|
||||||
const authors = await SELECT.from (Authors, a => {
|
|
||||||
a.name
|
|
||||||
})
|
|
||||||
expect(authors).to.eql([
|
|
||||||
{ name: 'Emily Brontë' },
|
|
||||||
{ name: 'Charlotte Brontë' },
|
|
||||||
{ name: 'Edgar Allen Poe' },
|
|
||||||
{ name: 'Richard Carpenter' },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should GET /Authors?$select=name&$expand=books($select=title)', async () => {
|
|
||||||
const {data:{value:authors}} = await GET ('/admin/Authors?$select=name&$expand=books($select=title)')
|
|
||||||
expect(authors).to.containSubset([
|
|
||||||
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
|
|
||||||
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
|
|
||||||
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
|
|
||||||
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should SELECT from Authors { name, books { title } }', async () => {
|
|
||||||
const authors = await SELECT.from(Authors, a => { a.name, a.books(b=>{ b.title }) })
|
|
||||||
expect(authors).to.eql([
|
|
||||||
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
|
|
||||||
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
|
|
||||||
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
|
|
||||||
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should GET /Authors?$expand=books', async () => {
|
|
||||||
const {data:{value:authors}} = await GET ('/admin/Authors?$select=name&$expand=books($select=title)')
|
|
||||||
expect(authors).to.containSubset([
|
|
||||||
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
|
|
||||||
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
|
|
||||||
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
|
|
||||||
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should SELECT from Authors { *, books{*} }', async () => {
|
|
||||||
const authors = await SELECT.from(Authors, a => { a('*'), a.books(b => b('*')) })
|
|
||||||
// const authors = await SELECT.from(Authors, ['*',{ref:['books'], expand:['*']}])
|
|
||||||
// const authors = await SELECT.from(Authors, [{ref:['*']},{ref:['books'], expand:[{ref:['*']}]}])
|
|
||||||
expect(authors).to.eql([
|
|
||||||
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
|
|
||||||
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
|
|
||||||
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
|
|
||||||
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user