Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b03529a4c | ||
|
|
bed293f1b2 | ||
|
|
50b13f60ce | ||
|
|
f889c27338 | ||
|
|
624c6c5c72 | ||
|
|
12fd91ca4f | ||
|
|
535981dc7e | ||
|
|
d15d0535d9 | ||
|
|
430d3a46c4 | ||
|
|
308e6b932a | ||
|
|
703d45fab0 | ||
|
|
63c21c5a96 | ||
|
|
e0c6b16b15 | ||
|
|
0771fc06e6 | ||
|
|
dc90cad8f4 | ||
|
|
f731a95bd1 | ||
|
|
2cd092be10 | ||
|
|
a1c2f32408 | ||
|
|
8a6a42f109 | ||
|
|
28cfceeaf0 | ||
|
|
879546829a | ||
|
|
b7e435d611 | ||
|
|
4f5f075697 | ||
|
|
710c83aa1c | ||
|
|
e9fbba9f46 | ||
|
|
0d96447d4b | ||
|
|
ed1510d173 | ||
|
|
d07b20a689 | ||
|
|
e6584c4c15 | ||
|
|
2a6fca177b | ||
|
|
d9058f9b51 | ||
|
|
affd1718ef | ||
|
|
5ff72c3f02 | ||
|
|
666001d564 | ||
|
|
e4214b0e40 | ||
|
|
ad76c699c9 | ||
|
|
0ad8baefc7 | ||
|
|
3f7b43346f | ||
|
|
6981841d86 | ||
|
|
18c29d8b67 | ||
|
|
02fc1a89a1 | ||
|
|
94b74d76ad | ||
|
|
b0b4412c7a | ||
|
|
70cfa82a35 | ||
|
|
d17d74f1ec | ||
|
|
9fbf7c69c9 | ||
|
|
f54a8cecb7 | ||
|
|
608e16090c | ||
|
|
bd0f514026 | ||
|
|
ee2fdd0989 | ||
|
|
92f7489def |
51
.eslintrc
51
.eslintrc
@@ -1,28 +1,31 @@
|
|||||||
{
|
{
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"plugin:@sap/cds/recommended",
|
||||||
"plugin:@sap/cds/recommended"
|
"eslint:recommended"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"es2022": true,
|
"es2022": true,
|
||||||
"node": true,
|
"node": true,
|
||||||
"jest": true,
|
"jest": true,
|
||||||
"mocha": true
|
"mocha": true
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"SELECT": true,
|
"SELECT": true,
|
||||||
"INSERT": true,
|
"INSERT": true,
|
||||||
"UPDATE": true,
|
"UPSERT": true,
|
||||||
"DELETE": true,
|
"UPDATE": true,
|
||||||
"CREATE": true,
|
"DELETE": true,
|
||||||
"DROP": true,
|
"CREATE": true,
|
||||||
"cds": true
|
"DROP": true,
|
||||||
},
|
"CDL": true,
|
||||||
"rules": {
|
"CQL": true,
|
||||||
"no-console": "off",
|
"cds": true
|
||||||
"require-atomic-updates": "off",
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-console": "off",
|
||||||
|
"require-atomic-updates": "off",
|
||||||
"require-await":"warn",
|
"require-await":"warn",
|
||||||
"no-unused-vars": ["warn", { "argsIgnorePattern": "_" }]
|
"no-unused-vars": ["warn", { "argsIgnorePattern": "_" }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
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
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
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
|
|
||||||
1
.registry/.gitignore
vendored
1
.registry/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
*.tgz
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
const { exec } = require ('child_process')
|
|
||||||
const isWin = process.platform === 'win32'
|
|
||||||
const express = require ('express')
|
|
||||||
const fs = require ('fs')
|
|
||||||
const app = express()
|
|
||||||
|
|
||||||
const { PORT=4444 } = process.env
|
|
||||||
const [,,port=PORT,scope='@capire'] = process.argv
|
|
||||||
const cwd = __dirname
|
|
||||||
|
|
||||||
// clean up on start (exit handler might not complete on Windows)
|
|
||||||
exec(isWin ? 'del *.tgz' : 'rm *.tgz', {cwd})
|
|
||||||
|
|
||||||
|
|
||||||
app.use('/-/:tarball', (req,res,next) => {
|
|
||||||
console.debug ('GET', req.params)
|
|
||||||
try {
|
|
||||||
const { tarball } = req.params
|
|
||||||
const pkgFull = tarball.substring(0, tarball.lastIndexOf('-'))
|
|
||||||
const [, pkg ] = /^\w+-(.+)/.exec(pkgFull)
|
|
||||||
fs.lstat(tarball,(err => {
|
|
||||||
if (err) console.debug (`npm pack ../${pkg}`)
|
|
||||||
if (err) exec(`npm pack ../${pkg}`,{cwd},next)
|
|
||||||
else next()
|
|
||||||
}))
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
res.sendStatus(500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.use('/-', express.static(__dirname))
|
|
||||||
|
|
||||||
app.get('/*', (req,res)=>{
|
|
||||||
const urlRegex = /^\/(@[\w-]+)\/(.+)/
|
|
||||||
const url = decodeURIComponent(req.url)
|
|
||||||
console.debug ('GET',url)
|
|
||||||
try {
|
|
||||||
if (!urlRegex.test(url)) return res.sendStatus(404)
|
|
||||||
const [, scpe, pkg ] = urlRegex.exec(url)
|
|
||||||
const package = require (`${scpe}/${pkg}/package.json`)
|
|
||||||
const tarball = `${scpe.slice(1)}-${pkg}-${package.version}.tgz`
|
|
||||||
// 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": `${server.url}/-/${tarball}`
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
if (e.code === 'MODULE_NOT_FOUND') return res.sendStatus(404)
|
|
||||||
console.error(e); throw e
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const server = app.listen(port, ()=>{
|
|
||||||
const url = server.url = `http://localhost:${server.address().port}`
|
|
||||||
console.log (`npm set ${scope}:registry=${url}`)
|
|
||||||
exec(`npm set ${scope}:registry=${url}`)
|
|
||||||
console.log (`${scope} registry listening on ${url}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
const _exit = ()=>{
|
|
||||||
server.close()
|
|
||||||
exec(`npm conf rm "${scope}:registry"`, ()=> { process.exit() })
|
|
||||||
}
|
|
||||||
|
|
||||||
process.on ('SIGTERM',_exit)
|
|
||||||
process.on ('SIGHUP',_exit)
|
|
||||||
process.on ('SIGINT',_exit)
|
|
||||||
process.on ('SIGUSR2',_exit)
|
|
||||||
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/cls.js",
|
"**/cds/lib/req/cds-context.js",
|
||||||
"**/odata-v4/okra/**"
|
"**/odata-v4/okra/**"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -26,10 +26,24 @@
|
|||||||
"<node_internals>/**",
|
"<node_internals>/**",
|
||||||
"**/node_modules/**",
|
"**/node_modules/**",
|
||||||
"**/cds/lib/lazy.js",
|
"**/cds/lib/lazy.js",
|
||||||
"**/cds/lib/req/cls.js",
|
"**/cds/lib/req/cds-context.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": [
|
||||||
{
|
{
|
||||||
|
|||||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -10,12 +10,13 @@
|
|||||||
"<node_internals>/**",
|
"<node_internals>/**",
|
||||||
"**/node_modules/**",
|
"**/node_modules/**",
|
||||||
"**/cds/lib/lazy.js",
|
"**/cds/lib/lazy.js",
|
||||||
"**/cds/lib/req/cls.js",
|
"**/cds/lib/req/cds-context.js",
|
||||||
"**/odata-v4/okra/**"
|
"**/odata-v4/okra/**"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"mochaExplorer.debuggerConfig": "Debug Mocha Tests",
|
||||||
"mochaExplorer.parallel": true,
|
"mochaExplorer.parallel": true,
|
||||||
"eslint.validate": [
|
"eslint.probe": [
|
||||||
"cds",
|
"cds",
|
||||||
"csn",
|
"csn",
|
||||||
"csv",
|
"csv",
|
||||||
@@ -23,4 +24,4 @@
|
|||||||
"tsv",
|
"tsv",
|
||||||
"tab"
|
"tab"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ ID;title;descr;author_ID;stock;price;currency_code;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;11
|
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;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;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;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;16
|
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;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;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;15
|
||||||
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;13
|
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;13
|
||||||
|
@@ -4,21 +4,21 @@
|
|||||||
* currencies, if not obtained through @capire/common.
|
* currencies, if not obtained through @capire/common.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = async (db)=>{
|
module.exports = async (tx)=>{
|
||||||
|
|
||||||
const has_common = db.model.definitions['sap.common.Currencies'].elements.numcode
|
const has_common = tx.model.definitions['sap.common.Currencies']?.elements.numcode
|
||||||
if (has_common) return
|
if (has_common) return
|
||||||
|
|
||||||
const already_filled = await db.exists('sap.common.Currencies',{code:'EUR'})
|
const already_filled = await tx.exists('sap.common.Currencies',{code:'EUR'})
|
||||||
if (already_filled) return
|
if (already_filled) return
|
||||||
|
|
||||||
await INSERT.into ('sap.common.Currencies') .columns (
|
await tx.run (INSERT.into ('sap.common.Currencies') .columns (
|
||||||
'code','symbol','name'
|
[ 'code', 'symbol', 'name' ]
|
||||||
) .rows (
|
) .rows (
|
||||||
[ 'EUR','€','Euro' ],
|
[ 'EUR', '€', 'Euro' ],
|
||||||
[ 'USD','$','US Dollar' ],
|
[ 'USD', '$', 'US Dollar' ],
|
||||||
[ 'GBP','£','British Pound' ],
|
[ 'GBP', '£', 'British Pound' ],
|
||||||
[ 'ILS','₪','Shekel' ],
|
[ 'ILS', '₪', 'Shekel' ],
|
||||||
[ 'JPY','¥','Yen' ],
|
[ 'JPY', '¥', 'Yen' ],
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ using { sap.capire.bookshop.Books } from '@capire/bookshop';
|
|||||||
using { ReviewsService.Reviews } from '@capire/reviews';
|
using { ReviewsService.Reviews } from '@capire/reviews';
|
||||||
extend Books with {
|
extend Books with {
|
||||||
reviews : Composition of many Reviews on reviews.subject = $self.ID;
|
reviews : Composition of many Reviews on reviews.subject = $self.ID;
|
||||||
|
|
||||||
|
@Common.Label : '{i18n>Rating}'
|
||||||
rating : Decimal;
|
rating : Decimal;
|
||||||
|
|
||||||
|
@Common.Label : '{i18n>NumberOfReviews}'
|
||||||
numberOfReviews : Integer;
|
numberOfReviews : Integer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,33 @@ Author = Author
|
|||||||
AuthorID = Author ID
|
AuthorID = Author ID
|
||||||
Stock = Stock
|
Stock = Stock
|
||||||
Name = Name
|
Name = Name
|
||||||
|
Description = Description
|
||||||
|
Image = Image
|
||||||
AuthorName = Author's 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
|
Age = Age
|
||||||
|
Lifetime = Lifetime
|
||||||
Authors = Authors
|
Authors = Authors
|
||||||
|
|
||||||
Order = Order
|
Order = Order
|
||||||
Orders = Orders
|
Orders = Orders
|
||||||
|
OrderNo = Order Number
|
||||||
|
OrderItems = Order Items
|
||||||
|
Customer = Customer
|
||||||
|
Product = Product
|
||||||
|
ProductID = Product ID
|
||||||
|
ProductTitle = Product Title
|
||||||
|
UnitPrice = Unit Price
|
||||||
|
Quantity = Quantity
|
||||||
|
|
||||||
Price = Price
|
Price = Price
|
||||||
|
Currency = Currency
|
||||||
|
Date = Date
|
||||||
|
Rating = Rating
|
||||||
|
NumberOfReviews = Number of Reviews
|
||||||
|
|
||||||
Genre = Genre
|
Genre = Genre
|
||||||
Genres = Genres
|
Genres = Genres
|
||||||
|
|||||||
@@ -43,5 +43,10 @@ extend sap.capire.bookshop.Authors with {
|
|||||||
virtual lifetime : String;
|
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
|
// Workaround for Fiori popup for asking user to enter a new UUID on Create
|
||||||
annotate AdminService.Authors with { ID @Core.Computed; }
|
annotate AdminService.Authors with { ID @Core.Computed; }
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ annotate AdminService.Books.texts with @(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
annotate AdminService.Books.texts with {
|
||||||
|
ID @UI.Hidden;
|
||||||
|
ID_texts @UI.Hidden;
|
||||||
|
};
|
||||||
|
|
||||||
// Add Value Help for Locales
|
// Add Value Help for Locales
|
||||||
annotate AdminService.Books.texts {
|
annotate AdminService.Books.texts {
|
||||||
locale @(
|
locale @(
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
using CatalogService from '@capire/bookstore';
|
using CatalogService from '@capire/bookstore';
|
||||||
|
|
||||||
|
using from '@sap/cds-pdf-export';
|
||||||
|
|
||||||
|
annotate CatalogService with @(
|
||||||
|
Capabilities: { SupportedFormats : [ 'application/pdf' ] },
|
||||||
|
PDF.Features: {
|
||||||
|
DocumentDescriptionReference : '/-pdf/',
|
||||||
|
DocumentDescriptionCollection : 'createDocumentDescription'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Books Object Page
|
// Books Object Page
|
||||||
@@ -33,7 +43,7 @@ annotate CatalogService.Books with @(UI : {
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Books Object Page
|
// Books List Page
|
||||||
//
|
//
|
||||||
annotate CatalogService.Books with @(UI : {
|
annotate CatalogService.Books with @(UI : {
|
||||||
SelectionFields : [
|
SelectionFields : [
|
||||||
@@ -52,9 +62,6 @@ annotate CatalogService.Books with @(UI : {
|
|||||||
},
|
},
|
||||||
{Value : genre.name},
|
{Value : genre.name},
|
||||||
{Value : price},
|
{Value : price},
|
||||||
{
|
{Value : currency.symbol},
|
||||||
Value : currency.symbol,
|
|
||||||
Label : ' '
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}, );
|
}, );
|
||||||
|
|||||||
@@ -4,57 +4,53 @@
|
|||||||
|
|
||||||
using { sap.capire.bookshop as my } from '@capire/bookstore';
|
using { sap.capire.bookshop as my } from '@capire/bookstore';
|
||||||
using { sap.common } from '@capire/common';
|
using { sap.common } from '@capire/common';
|
||||||
|
using { sap.common.Currencies } from '@sap/cds/common';
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Books Lists
|
// Books Lists
|
||||||
//
|
//
|
||||||
annotate my.Books with @(
|
annotate my.Books with @(
|
||||||
Common.SemanticKey : [ID],
|
Common.SemanticKey : [ID],
|
||||||
UI : {
|
UI : {
|
||||||
Identification : [{Value : title}],
|
Identification : [{ Value: title }],
|
||||||
SelectionFields : [
|
SelectionFields : [
|
||||||
ID,
|
ID,
|
||||||
author_ID,
|
author_ID,
|
||||||
price,
|
price,
|
||||||
currency_code
|
currency_code
|
||||||
],
|
],
|
||||||
LineItem : [
|
LineItem : [
|
||||||
{
|
{ Value: ID, Label: '{i18n>Title}' },
|
||||||
Value : ID,
|
{ Value: author.ID, Label: '{i18n>Author}' },
|
||||||
Label : '{i18n>Title}'
|
{ Value: genre.name },
|
||||||
},
|
{ Value: stock },
|
||||||
{
|
{ Value: price },
|
||||||
Value : author.ID,
|
{ Value: currency.symbol },
|
||||||
Label : '{i18n>Author}'
|
]
|
||||||
},
|
}
|
||||||
{Value : genre.name},
|
|
||||||
{Value : stock},
|
|
||||||
{Value : price},
|
|
||||||
{
|
|
||||||
Value : currency.symbol,
|
|
||||||
Label : ' '
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
ID @Common: {
|
ID @Common: {
|
||||||
SemanticObject : 'Books',
|
SemanticObject : 'Books',
|
||||||
Text: title,
|
Text: title,
|
||||||
TextArrangement : #TextOnly
|
TextArrangement : #TextOnly
|
||||||
};
|
};
|
||||||
author @ValueList.entity : 'Authors';
|
author @ValueList.entity : 'Authors';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
annotate Currencies with {
|
||||||
|
symbol @Common.Label : '{i18n>Currency}';
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Books Details
|
// Books Details
|
||||||
//
|
//
|
||||||
annotate my.Books with @(UI : {HeaderInfo : {
|
annotate my.Books with @(UI : {HeaderInfo : {
|
||||||
TypeName : '{i18n>Book}',
|
TypeName : '{i18n>Book}',
|
||||||
TypeNamePlural : '{i18n>Books}',
|
TypeNamePlural : '{i18n>Books}',
|
||||||
Title : {Value : title},
|
Title : { Value: title },
|
||||||
Description : {Value : author.name}
|
Description : { Value: author.name }
|
||||||
}, });
|
}, });
|
||||||
|
|
||||||
|
|
||||||
@@ -63,19 +59,14 @@ annotate my.Books with @(UI : {HeaderInfo : {
|
|||||||
// Books Elements
|
// Books Elements
|
||||||
//
|
//
|
||||||
annotate my.Books with {
|
annotate my.Books with {
|
||||||
ID @title : '{i18n>ID}';
|
ID @title: '{i18n>ID}';
|
||||||
title @title : '{i18n>Title}';
|
title @title: '{i18n>Title}';
|
||||||
genre @title : '{i18n>Genre}' @Common : {
|
genre @title: '{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly };
|
||||||
Text : genre.name,
|
author @title: '{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
|
||||||
TextArrangement : #TextOnly
|
price @title: '{i18n>Price}' @Measures.ISOCurrency : currency_code;
|
||||||
};
|
stock @title: '{i18n>Stock}';
|
||||||
author @title : '{i18n>Author}' @Common : {
|
descr @title: '{i18n>Description}' @UI.MultiLineText;
|
||||||
Text : author.name,
|
image @title: '{i18n>Image}';
|
||||||
TextArrangement : #TextOnly
|
|
||||||
};
|
|
||||||
price @title : '{i18n>Price}' @Measures.ISOCurrency : currency_code;
|
|
||||||
stock @title : '{i18n>Stock}';
|
|
||||||
descr @UI.MultiLineText;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -83,36 +74,40 @@ annotate my.Books with {
|
|||||||
// Genres List
|
// Genres List
|
||||||
//
|
//
|
||||||
annotate my.Genres with @(
|
annotate my.Genres with @(
|
||||||
Common.SemanticKey : [name],
|
Common.SemanticKey : [name],
|
||||||
UI : {
|
UI : {
|
||||||
SelectionFields : [name],
|
SelectionFields : [name],
|
||||||
LineItem : [
|
LineItem : [
|
||||||
{Value : name},
|
{ Value: name },
|
||||||
{
|
{
|
||||||
Value : parent.name,
|
Value : parent.name,
|
||||||
Label : 'Main Genre'
|
Label: 'Main Genre'
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
annotate my.Genres with {
|
||||||
|
ID @Common.Text : name @Common.TextArrangement : #TextOnly;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Genre Details
|
// Genre Details
|
||||||
//
|
//
|
||||||
annotate my.Genres with @(UI : {
|
annotate my.Genres with @(UI : {
|
||||||
Identification : [{Value : name}],
|
Identification : [{ Value: name}],
|
||||||
HeaderInfo : {
|
HeaderInfo : {
|
||||||
TypeName : '{i18n>Genre}',
|
TypeName : '{i18n>Genre}',
|
||||||
TypeNamePlural : '{i18n>Genres}',
|
TypeNamePlural : '{i18n>Genres}',
|
||||||
Title : {Value : name},
|
Title : { Value: name },
|
||||||
Description : {Value : ID}
|
Description : { Value: ID }
|
||||||
},
|
},
|
||||||
Facets : [{
|
Facets : [{
|
||||||
$Type : 'UI.ReferenceFacet',
|
$Type : 'UI.ReferenceFacet',
|
||||||
Label : '{i18n>SubGenres}',
|
Label : '{i18n>SubGenres}',
|
||||||
Target : 'children/@UI.LineItem'
|
Target : 'children/@UI.LineItem'
|
||||||
}, ],
|
}, ],
|
||||||
});
|
});
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -120,8 +115,8 @@ annotate my.Genres with @(UI : {
|
|||||||
// Genres Elements
|
// Genres Elements
|
||||||
//
|
//
|
||||||
annotate my.Genres with {
|
annotate my.Genres with {
|
||||||
ID @title : '{i18n>ID}';
|
ID @title: '{i18n>ID}';
|
||||||
name @title : '{i18n>Genre}';
|
name @title: '{i18n>Genre}';
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -129,24 +124,24 @@ annotate my.Genres with {
|
|||||||
// Authors List
|
// Authors List
|
||||||
//
|
//
|
||||||
annotate my.Authors with @(
|
annotate my.Authors with @(
|
||||||
Common.SemanticKey : [ID],
|
Common.SemanticKey : [ID],
|
||||||
UI : {
|
UI : {
|
||||||
Identification : [{Value : name}],
|
Identification : [{ Value: name}],
|
||||||
SelectionFields : [name],
|
SelectionFields : [name],
|
||||||
LineItem : [
|
LineItem : [
|
||||||
{Value : ID},
|
{ Value: ID },
|
||||||
{Value : dateOfBirth},
|
{ Value: dateOfBirth },
|
||||||
{Value : dateOfDeath},
|
{ Value: dateOfDeath },
|
||||||
{Value : placeOfBirth},
|
{ Value: placeOfBirth },
|
||||||
{Value : placeOfDeath},
|
{ Value: placeOfDeath },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
ID @Common: {
|
ID @Common: {
|
||||||
SemanticObject : 'Authors',
|
SemanticObject : 'Authors',
|
||||||
Text: name,
|
Text: name,
|
||||||
TextArrangement : #TextOnly,
|
TextArrangement : #TextOnly,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -154,16 +149,16 @@ annotate my.Authors with @(
|
|||||||
// Author Details
|
// Author Details
|
||||||
//
|
//
|
||||||
annotate my.Authors with @(UI : {
|
annotate my.Authors with @(UI : {
|
||||||
HeaderInfo : {
|
HeaderInfo : {
|
||||||
TypeName : '{i18n>Author}',
|
TypeName : '{i18n>Author}',
|
||||||
TypeNamePlural : '{i18n>Authors}',
|
TypeNamePlural : '{i18n>Authors}',
|
||||||
Title : {Value : name},
|
Title : { Value: name },
|
||||||
Description : {Value : dateOfBirth}
|
Description : { Value: dateOfBirth }
|
||||||
},
|
},
|
||||||
Facets : [{
|
Facets : [{
|
||||||
$Type : 'UI.ReferenceFacet',
|
$Type : 'UI.ReferenceFacet',
|
||||||
Target : 'books/@UI.LineItem'
|
Target : 'books/@UI.LineItem'
|
||||||
}, ],
|
}, ],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -172,12 +167,12 @@ annotate my.Authors with @(UI : {
|
|||||||
// Authors Elements
|
// Authors Elements
|
||||||
//
|
//
|
||||||
annotate my.Authors with {
|
annotate my.Authors with {
|
||||||
ID @title : '{i18n>ID}';
|
ID @title: '{i18n>ID}';
|
||||||
name @title : '{i18n>Name}';
|
name @title: '{i18n>Name}';
|
||||||
dateOfBirth @title : '{i18n>DateOfBirth}';
|
dateOfBirth @title: '{i18n>DateOfBirth}';
|
||||||
dateOfDeath @title : '{i18n>DateOfDeath}';
|
dateOfDeath @title: '{i18n>DateOfDeath}';
|
||||||
placeOfBirth @title : '{i18n>PlaceOfBirth}';
|
placeOfBirth @title: '{i18n>PlaceOfBirth}';
|
||||||
placeOfDeath @title : '{i18n>PlaceOfDeath}';
|
placeOfDeath @title: '{i18n>PlaceOfDeath}';
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -185,18 +180,18 @@ annotate my.Authors with {
|
|||||||
// Languages List
|
// Languages List
|
||||||
//
|
//
|
||||||
annotate common.Languages with @(
|
annotate common.Languages with @(
|
||||||
Common.SemanticKey : [code],
|
Common.SemanticKey : [code],
|
||||||
Identification : [{Value : code}],
|
Identification : [{ Value: code}],
|
||||||
UI : {
|
UI : {
|
||||||
SelectionFields : [
|
SelectionFields : [
|
||||||
name,
|
name,
|
||||||
descr
|
descr
|
||||||
],
|
],
|
||||||
LineItem : [
|
LineItem : [
|
||||||
{Value : code},
|
{ Value: code },
|
||||||
{Value : name},
|
{ Value: name },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -204,22 +199,22 @@ annotate common.Languages with @(
|
|||||||
// Language Details
|
// Language Details
|
||||||
//
|
//
|
||||||
annotate common.Languages with @(UI : {
|
annotate common.Languages with @(UI : {
|
||||||
HeaderInfo : {
|
HeaderInfo : {
|
||||||
TypeName : '{i18n>Language}',
|
TypeName : '{i18n>Language}',
|
||||||
TypeNamePlural : '{i18n>Languages}',
|
TypeNamePlural : '{i18n>Languages}',
|
||||||
Title : {Value : name},
|
Title : { Value: name },
|
||||||
Description : {Value : descr}
|
Description : { Value: descr }
|
||||||
},
|
},
|
||||||
Facets : [{
|
Facets : [{
|
||||||
$Type : 'UI.ReferenceFacet',
|
$Type : 'UI.ReferenceFacet',
|
||||||
Label : '{i18n>Details}',
|
Label : '{i18n>Details}',
|
||||||
Target : '@UI.FieldGroup#Details'
|
Target : '@UI.FieldGroup#Details'
|
||||||
}, ],
|
}, ],
|
||||||
FieldGroup #Details : {Data : [
|
FieldGroup #Details : {Data : [
|
||||||
{Value : code},
|
{ Value: code },
|
||||||
{Value : name},
|
{ Value: name },
|
||||||
{Value : descr}
|
{ Value: descr }
|
||||||
]},
|
]},
|
||||||
});
|
});
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -227,19 +222,19 @@ annotate common.Languages with @(UI : {
|
|||||||
// Currencies List
|
// Currencies List
|
||||||
//
|
//
|
||||||
annotate common.Currencies with @(
|
annotate common.Currencies with @(
|
||||||
Common.SemanticKey : [code],
|
Common.SemanticKey : [code],
|
||||||
Identification : [{Value : code}],
|
Identification : [{ Value: code}],
|
||||||
UI : {
|
UI : {
|
||||||
SelectionFields : [
|
SelectionFields : [
|
||||||
name,
|
name,
|
||||||
descr
|
descr
|
||||||
],
|
],
|
||||||
LineItem : [
|
LineItem : [
|
||||||
{Value : descr},
|
{ Value: descr },
|
||||||
{Value : symbol},
|
{ Value: symbol },
|
||||||
{Value : code},
|
{ Value: code },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -247,35 +242,35 @@ annotate common.Currencies with @(
|
|||||||
// Currency Details
|
// Currency Details
|
||||||
//
|
//
|
||||||
annotate common.Currencies with @(UI : {
|
annotate common.Currencies with @(UI : {
|
||||||
HeaderInfo : {
|
HeaderInfo : {
|
||||||
TypeName : '{i18n>Currency}',
|
TypeName : '{i18n>Currency}',
|
||||||
TypeNamePlural : '{i18n>Currencies}',
|
TypeNamePlural : '{i18n>Currencies}',
|
||||||
Title : {Value : descr},
|
Title : { Value: descr },
|
||||||
Description : {Value : code}
|
Description : { Value: code }
|
||||||
|
},
|
||||||
|
Facets : [
|
||||||
|
{
|
||||||
|
$Type : 'UI.ReferenceFacet',
|
||||||
|
Label : '{i18n>Details}',
|
||||||
|
Target : '@UI.FieldGroup#Details'
|
||||||
},
|
},
|
||||||
Facets : [
|
{
|
||||||
{
|
$Type : 'UI.ReferenceFacet',
|
||||||
$Type : 'UI.ReferenceFacet',
|
Label : '{i18n>Extended}',
|
||||||
Label : '{i18n>Details}',
|
Target : '@UI.FieldGroup#Extended'
|
||||||
Target : '@UI.FieldGroup#Details'
|
},
|
||||||
},
|
],
|
||||||
{
|
FieldGroup #Details : {Data : [
|
||||||
$Type : 'UI.ReferenceFacet',
|
{ Value: name },
|
||||||
Label : '{i18n>Extended}',
|
{ Value: symbol },
|
||||||
Target : '@UI.FieldGroup#Extended'
|
{ Value: code },
|
||||||
},
|
{ Value: descr }
|
||||||
],
|
]},
|
||||||
FieldGroup #Details : {Data : [
|
FieldGroup #Extended : {Data : [
|
||||||
{Value : name},
|
{ Value: numcode },
|
||||||
{Value : symbol},
|
{ Value: minor },
|
||||||
{Value : code},
|
{ Value: exponent }
|
||||||
{Value : descr}
|
]},
|
||||||
]},
|
|
||||||
FieldGroup #Extended : {Data : [
|
|
||||||
{Value : numcode},
|
|
||||||
{Value : minor},
|
|
||||||
{Value : exponent}
|
|
||||||
]},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -283,7 +278,7 @@ annotate common.Currencies with @(UI : {
|
|||||||
// Currencies Elements
|
// Currencies Elements
|
||||||
//
|
//
|
||||||
annotate common.Currencies with {
|
annotate common.Currencies with {
|
||||||
numcode @title : '{i18n>NumCode}';
|
numcode @title: '{i18n>NumCode}';
|
||||||
minor @title : '{i18n>MinorUnit}';
|
minor @title: '{i18n>MinorUnit}';
|
||||||
exponent @title : '{i18n>Exponent}';
|
exponent @title: '{i18n>Exponent}';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,16 @@
|
|||||||
"[production]": {
|
"[production]": {
|
||||||
"model": "db/hana"
|
"model": "db/hana"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"pdf": {
|
||||||
|
"kind": "btp-adobe-forms",
|
||||||
|
"vcap": {
|
||||||
|
"label": "adsrestapi"
|
||||||
|
},
|
||||||
|
"[pdfme]": {
|
||||||
|
"kind": "export-pdf",
|
||||||
|
"impl": "./pdfme.js"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hana": {
|
"hana": {
|
||||||
|
|||||||
38
fiori/pdfme.js
Normal file
38
fiori/pdfme.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const { generate, BLANK_PDF } = require("@pdfme/generator");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate PDF with @pdfme/generator library
|
||||||
|
*/
|
||||||
|
module.exports = async (data, headers) => {
|
||||||
|
|
||||||
|
let inputs = data
|
||||||
|
|
||||||
|
let x = 0, y = 0;
|
||||||
|
const width = 30;
|
||||||
|
const height = 5;
|
||||||
|
|
||||||
|
const tableSchema = {}
|
||||||
|
for (const entry of headers) {
|
||||||
|
x += width;
|
||||||
|
tableSchema[entry.Name] = {
|
||||||
|
type: 'text',
|
||||||
|
position: { x, y: 10 },
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const row of data) {
|
||||||
|
for (const [key, value] of Object.entries(row)) {
|
||||||
|
if (typeof value !== 'string') row[key] = ''+value // stringify
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = {
|
||||||
|
basePdf: BLANK_PDF,
|
||||||
|
schemas: [tableSchema]
|
||||||
|
};
|
||||||
|
const pdf = await generate({ template, inputs });
|
||||||
|
|
||||||
|
return pdf;
|
||||||
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
const cds = require("@sap/cds")
|
|
||||||
|
|
||||||
// install OData v2 adapter
|
// install OData v2 adapter
|
||||||
|
const cds = require("@sap/cds")
|
||||||
const proxy = require('@sap/cds-odata-v2-adapter-proxy')
|
const proxy = require('@sap/cds-odata-v2-adapter-proxy')
|
||||||
const proxyOpts = global.it ? { target:'auto' } : {} // for tests, set 'auto' to detect port dynamically
|
const opts = global.it ? { target:'auto' } : {} // for tests, set 'auto' to detect port dynamically
|
||||||
cds.on('bootstrap', app => app.use(proxy(proxyOpts)))
|
cds.on('bootstrap', app => app.use(proxy(opts))) // install proxy
|
||||||
|
cds.log('cov2ap','silent') // suppress anoying log outpout, e.g. for `npm run mocha -- --reporter nyan`
|
||||||
|
|
||||||
module.exports = require('@capire/bookstore/server.js')
|
module.exports = require('@capire/bookstore/server.js')
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
module.exports = class say {
|
module.exports = class say {
|
||||||
hello(req) { return `Hello ${req.data.to}!` }
|
hello(req) {
|
||||||
|
let {to} = req.data
|
||||||
|
if (to === 'me') to = require('os').userInfo().username
|
||||||
|
return `Hello ${to}!`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
76
loggers/app/loggers.html
Normal file
76
loggers/app/loggers.html
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<!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>
|
||||||
22
loggers/package.json
Normal file
22
loggers/package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"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 run",
|
||||||
|
"watch": "cds watch"
|
||||||
|
},
|
||||||
|
"cds": {
|
||||||
|
"requires": {
|
||||||
|
"db": "sql"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
loggers/readme.md
Normal file
11
loggers/readme.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# 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`
|
||||||
3
loggers/srv/dummy.cds
Normal file
3
loggers/srv/dummy.cds
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
service Sue {
|
||||||
|
entity Dummy { key ID: UUID; title: String; }
|
||||||
|
}
|
||||||
20
loggers/srv/loggers.cds
Normal file
20
loggers/srv/loggers.cds
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@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;
|
||||||
|
|
||||||
|
}
|
||||||
56
loggers/srv/loggers.js
Normal file
56
loggers/srv/loggers.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
const cds = require ('@sap/cds/lib')
|
||||||
|
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' ]
|
||||||
18
loggers/test/requests.http
Normal file
18
loggers/test/requests.http
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
@@ -40,7 +40,7 @@ module.exports = srv => {
|
|||||||
req.reject(404, 'Media not found for the ID')
|
req.reject(404, 'Media not found for the ID')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const decodedMedia = new Buffer(
|
const decodedMedia = Buffer.from(
|
||||||
mediaObj.media.split(';base64,').pop(),
|
mediaObj.media.split(';base64,').pop(),
|
||||||
'base64'
|
'base64'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,23 +16,24 @@ using { OrdersService } from '../srv/orders-service';
|
|||||||
@odata.draft.enabled
|
@odata.draft.enabled
|
||||||
annotate OrdersService.Orders with @(
|
annotate OrdersService.Orders with @(
|
||||||
UI: {
|
UI: {
|
||||||
SelectionFields: [ createdAt, createdBy ],
|
SelectionFields: [ createdBy ],
|
||||||
LineItem: [
|
LineItem: [
|
||||||
{Value: OrderNo, Label:'OrderNo'},
|
{Value: OrderNo, Label:'{i18n>OrderNo}'},
|
||||||
{Value: buyer, Label:'Customer'},
|
{Value: buyer, Label:'{i18n>Customer}'},
|
||||||
{Value: createdAt, Label:'Date'}
|
{Value: currency.symbol, Label:'{i18n>Currency}'},
|
||||||
|
{Value: createdAt, Label:'{i18n>Date}'},
|
||||||
],
|
],
|
||||||
HeaderInfo: {
|
HeaderInfo: {
|
||||||
TypeName: 'Order', TypeNamePlural: 'Orders',
|
TypeName: '{i18n>Order}', TypeNamePlural: '{i18n>Orders}',
|
||||||
Title: {
|
Title: {
|
||||||
Label: 'Order number ', //A label is possible but it is not considered on the ObjectPage yet
|
Label: '{i18n>OrderNo}', //A label is possible but it is not considered on the ObjectPage yet
|
||||||
Value: OrderNo
|
Value: OrderNo
|
||||||
},
|
},
|
||||||
Description: {Value: createdBy}
|
Description: {Value: createdBy}
|
||||||
},
|
},
|
||||||
Identification: [ //Is the main field group
|
Identification: [ //Is the main field group
|
||||||
{Value: createdBy, Label:'Customer'},
|
{Value: createdBy, Label:'{i18n>Customer}'},
|
||||||
{Value: createdAt, Label:'Date'},
|
{Value: createdAt, Label:'{i18n>Date}'},
|
||||||
{Value: OrderNo },
|
{Value: OrderNo },
|
||||||
],
|
],
|
||||||
HeaderFacets: [
|
HeaderFacets: [
|
||||||
@@ -45,7 +46,7 @@ annotate OrdersService.Orders with @(
|
|||||||
],
|
],
|
||||||
FieldGroup#Details: {
|
FieldGroup#Details: {
|
||||||
Data: [
|
Data: [
|
||||||
{Value: currency.code, Label:'Currency'}
|
{Value: currency.code, Label:'{i18n>Currency}'}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
FieldGroup#Created: {
|
FieldGroup#Created: {
|
||||||
@@ -64,6 +65,7 @@ annotate OrdersService.Orders with @(
|
|||||||
) {
|
) {
|
||||||
createdAt @UI.HiddenFilter:false;
|
createdAt @UI.HiddenFilter:false;
|
||||||
createdBy @UI.HiddenFilter:false;
|
createdBy @UI.HiddenFilter:false;
|
||||||
|
ID @UI.Hidden;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -71,15 +73,15 @@ annotate OrdersService.Orders with @(
|
|||||||
annotate OrdersService.Orders.Items with @(
|
annotate OrdersService.Orders.Items with @(
|
||||||
UI: {
|
UI: {
|
||||||
LineItem: [
|
LineItem: [
|
||||||
{Value: product_ID, Label:'Product ID'},
|
{Value: product_ID, Label:'{i18n>ProductID}'},
|
||||||
{Value: title, Label:'Product Title'},
|
{Value: title, Label:'{i18n>ProductTitle}'},
|
||||||
{Value: price, Label:'Unit Price'},
|
{Value: price, Label:'{i18n>UnitPrice}'},
|
||||||
{Value: quantity, Label:'Quantity'},
|
{Value: quantity, Label:'{i18n>Quantity}'},
|
||||||
],
|
],
|
||||||
Identification: [ //Is the main field group
|
Identification: [ //Is the main field group
|
||||||
{Value: quantity, Label:'Quantity'},
|
{Value: quantity, Label:'{i18n>Quantity}'},
|
||||||
{Value: title, Label:'Product'},
|
{Value: title, Label:'{i18n>Product}'},
|
||||||
{Value: price, Label:'Unit Price'},
|
{Value: price, Label:'{i18n>UnitPrice}'},
|
||||||
],
|
],
|
||||||
Facets: [
|
Facets: [
|
||||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>OrderItems}', Target: '@UI.Identification'},
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>OrderItems}', Target: '@UI.Identification'},
|
||||||
@@ -89,4 +91,7 @@ annotate OrdersService.Orders.Items with @(
|
|||||||
quantity @(
|
quantity @(
|
||||||
Common.FieldControl: #Mandatory
|
Common.FieldControl: #Mandatory
|
||||||
);
|
);
|
||||||
|
ID @UI.Hidden;
|
||||||
|
up_ @UI.Hidden;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
applications: {
|
applications: {
|
||||||
"manage-orders": {
|
"manage-orders": {
|
||||||
title: "Manage Orders",
|
title: "Manage Orders",
|
||||||
description: "... testing FE v42",
|
description: "CAP Sample App",
|
||||||
additionalInformation: "SAPUI5.Component=orders",
|
additionalInformation: "SAPUI5.Component=orders",
|
||||||
applicationType : "URL",
|
applicationType : "URL",
|
||||||
url: "/orders/webapp",
|
url: "/orders/webapp",
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
"sap.app": {
|
"sap.app": {
|
||||||
"id": "orders",
|
"id": "orders",
|
||||||
"type": "application",
|
"type": "application",
|
||||||
"title": "Order Books",
|
"title": "Order Management",
|
||||||
"description": "Sample Application",
|
"description": "CAP Sample Application",
|
||||||
"i18n": "i18n/i18n.properties",
|
"i18n": "i18n/i18n.properties",
|
||||||
"dataSources": {
|
"dataSources": {
|
||||||
"OrdersService": {
|
"OrdersService": {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using { Currency, User, managed, cuid } from '@sap/cds/common';
|
|||||||
namespace sap.capire.orders;
|
namespace sap.capire.orders;
|
||||||
|
|
||||||
entity Orders : cuid, managed {
|
entity Orders : cuid, managed {
|
||||||
OrderNo : String @title:'Order Number'; //> readable key
|
OrderNo : String(22) @title:'Order Number'; //> readable key
|
||||||
Items : Composition of many {
|
Items : Composition of many {
|
||||||
key ID : UUID;
|
key ID : UUID;
|
||||||
product : Association to Products;
|
product : Association to Products;
|
||||||
|
|||||||
3539
package-lock.json
generated
3539
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@@ -8,10 +8,20 @@
|
|||||||
"@sap/cds": ">=5.5.3"
|
"@sap/cds": ">=5.5.3"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"./*/"
|
"./bookshop",
|
||||||
|
"./bookstore",
|
||||||
|
"./common",
|
||||||
|
"./data-viewer",
|
||||||
|
"./fiori",
|
||||||
|
"./hello",
|
||||||
|
"./media",
|
||||||
|
"./orders",
|
||||||
|
"./loggers",
|
||||||
|
"./reviews"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"axios": "^0",
|
"@sap/eslint-plugin-cds": "^2.6.1",
|
||||||
|
"axios": "^1",
|
||||||
"chai": "^4.3.4",
|
"chai": "^4.3.4",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
"chai-subset": "^1.6.0",
|
"chai-subset": "^1.6.0",
|
||||||
@@ -20,13 +30,12 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules",
|
"cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules",
|
||||||
"registry": "node .registry/server.js",
|
|
||||||
"bookshop": "cds watch bookshop",
|
"bookshop": "cds watch bookshop",
|
||||||
"fiori": "cds watch fiori",
|
"fiori": "cds watch fiori",
|
||||||
"hello": "cds watch hello",
|
"hello": "cds watch hello",
|
||||||
"media": "cds watch media",
|
"media": "cds watch media",
|
||||||
"mocha": "npx mocha || echo",
|
"mocha": "CDS_TEST_SILENT=y npx mocha",
|
||||||
"jest": "npx jest",
|
"jest": "npx jest --silent",
|
||||||
"start": "cds watch fiori",
|
"start": "cds watch fiori",
|
||||||
"test": "npm run jest -- --silent",
|
"test": "npm run jest -- --silent",
|
||||||
"test:hello": "cd hello && npm test"
|
"test:hello": "cd hello && npm test"
|
||||||
@@ -39,7 +48,8 @@
|
|||||||
},
|
},
|
||||||
"mocha": {
|
"mocha": {
|
||||||
"recursive": true,
|
"recursive": true,
|
||||||
"parallel": true
|
"parallel": true,
|
||||||
|
"timeout": 6666
|
||||||
},
|
},
|
||||||
"license": "SAP SAMPLE CODE LICENSE",
|
"license": "SAP SAMPLE CODE LICENSE",
|
||||||
"private": true
|
"private": true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Overview of Samples
|
# Overview of Samples
|
||||||
|
|
||||||
The following list gives an overview of the samples provided in subdirectories.
|
The following list gives an overview of the samples provided in subdirectories.
|
||||||
Each sub directory essentially is an individual npm package arranged in an [all-in-one monorepo](all-in-one-monorepo) umbrella setup.
|
Each sub directory essentially is an individual npm package arranged in an [all-in-one monorepo](#all-in-one-monorepo) umbrella setup.
|
||||||
|
|
||||||
|
|
||||||
## [@capire/hello-world](hello)
|
## [@capire/hello-world](hello)
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ const cds = require('@sap/cds/lib')
|
|||||||
describe('cap/samples - Custom Handlers', () => {
|
describe('cap/samples - Custom Handlers', () => {
|
||||||
|
|
||||||
const { GET, POST, expect } = cds.test(__dirname+'/../bookshop')
|
const { GET, POST, expect } = cds.test(__dirname+'/../bookshop')
|
||||||
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
beforeAll(()=>{
|
||||||
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
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 POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
|
await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds/lib')
|
||||||
|
|
||||||
describe('cap/samples - Fiori APIs - v2', () => {
|
describe('cap/samples - Fiori APIs - v2', function() {
|
||||||
|
|
||||||
const { GET, expect, axios } = cds.test ('@capire/fiori', '--with-mocks')
|
const { GET, expect, axios } = cds.test ('@capire/fiori', '--with-mocks')
|
||||||
axios.defaults.auth = { username: 'alice', password: 'admin' }
|
axios.defaults.auth = { username: 'alice', password: 'admin' }
|
||||||
|
|
||||||
|
// if (this.timeout) this.timeout(1e6)
|
||||||
|
|
||||||
it('serves $metadata documents in v2', async () => {
|
it('serves $metadata documents in v2', async () => {
|
||||||
const { headers, data } = await GET `/v2/browse/$metadata`
|
const { headers, data } = await GET `/v2/browse/$metadata`
|
||||||
expect(headers).to.contain({
|
expect(headers).to.contain({
|
||||||
|
|||||||
@@ -16,21 +16,21 @@ describe('cap/samples - Hierarchical Data', ()=>{
|
|||||||
before ('bootstrap sqlite in-memory db...', async()=>{
|
before ('bootstrap sqlite in-memory db...', async()=>{
|
||||||
await cds.deploy (model) .to ('sqlite::memory:')
|
await cds.deploy (model) .to ('sqlite::memory:')
|
||||||
expect (cds.db) .to.exist
|
expect (cds.db) .to.exist
|
||||||
expect (cds.db.model) .to.exist
|
expect (cds.db.model) .to.exist
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('supports deeply nested inserts', ()=> INSERT.into (Cats,
|
it ('supports deeply nested inserts', ()=> INSERT.into (Cats,
|
||||||
{ ID:100, name:'Some Cats...', children:[
|
{ ID:100, name:'Some Cats...', children:[
|
||||||
{ ID:101, name:'Cat', children:[
|
{ ID:101, name:'Cat', children:[
|
||||||
{ ID:102, name:'Kitty', children:[
|
{ ID:102, name:'Kitty', children:[
|
||||||
{ ID:103, name:'Kitty Cat', children:[
|
{ ID:103, name:'Kitty Cat', children:[
|
||||||
{ ID:104, name:'Aristocat' } ]},
|
{ ID:104, name:'Aristocat' } ]},
|
||||||
{ ID:105, name:'Kitty Bat' } ]},
|
{ ID:105, name:'Kitty Bat' } ]},
|
||||||
{ ID:106, name:'Catwoman', children:[
|
{ ID:106, name:'Catwoman', children:[
|
||||||
{ ID:107, name:'Catalina' } ]} ]},
|
{ ID:107, name:'Catalina' } ]} ]},
|
||||||
{ ID:108, name:'Catweazle' }
|
{ ID:108, name:'Catweazle' }
|
||||||
]}
|
]}
|
||||||
))
|
))
|
||||||
|
|
||||||
it ('supports nested reads', async()=>{
|
it ('supports nested reads', async()=>{
|
||||||
if (require('semver').gte(cds.version, '5.9.0')) {
|
if (require('semver').gte(cds.version, '5.9.0')) {
|
||||||
@@ -101,12 +101,12 @@ describe('cap/samples - Hierarchical Data', ()=>{
|
|||||||
})
|
})
|
||||||
|
|
||||||
it ('supports cascaded deletes', async()=>{
|
it ('supports cascaded deletes', async()=>{
|
||||||
const affectedRows = await DELETE.from (Cats) .where ({ID:[102,106]})
|
const affectedRows = await DELETE.from (Cats) .where ({ID:[102,106]})
|
||||||
expect (affectedRows) .to.be.greaterThan (0)
|
expect (affectedRows) .to.be.greaterThan (0)
|
||||||
const expected = [
|
const expected = [
|
||||||
{ ID:100, name:'Some Cats...' },
|
{ ID:100, name:'Some Cats...' },
|
||||||
{ ID:101, name:'Cat' },
|
{ ID:101, name:'Cat' },
|
||||||
{ ID:108, name:'Catweazle' }
|
{ ID:108, name:'Catweazle' }
|
||||||
]
|
]
|
||||||
expect ( await SELECT`ID,name`.from(Cats) ).to.eql (expected)
|
expect ( await SELECT`ID,name`.from(Cats) ).to.eql (expected)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
const cds = require('@sap/cds/lib')
|
||||||
|
|
||||||
describe('cap/samples - Localized Data', () => {
|
describe('cap/samples - Localized Data', () => {
|
||||||
|
|
||||||
const { GET, expect, cds } = require('@sap/cds/lib').test (__dirname)
|
const { GET, expect } = cds.test (__dirname)
|
||||||
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
beforeAll(()=>{
|
||||||
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
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' }})
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds/lib')
|
||||||
const {resolve} = require('path')
|
|
||||||
|
|
||||||
describe('cap/samples - Messaging', ()=>{
|
describe('cap/samples - Messaging', ()=>{
|
||||||
|
|
||||||
const { expect } = cds.test
|
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'
|
||||||
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
|
beforeAll(()=>{
|
||||||
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
|
cds.User.default = cds.User.Privileged // hard core monkey patch
|
||||||
|
})
|
||||||
beforeAll(() => { cds.root = resolve(__dirname, '..') })
|
|
||||||
afterAll(() => { cds.root = process.cwd() })
|
|
||||||
|
|
||||||
it ('should bootstrap sqlite in-memory db', async()=>{
|
it ('should bootstrap sqlite in-memory db', async()=>{
|
||||||
const db = await cds.deploy (_model) .to ('sqlite::memory:')
|
const db = await cds.deploy (_model) .to ('sqlite::memory:')
|
||||||
@@ -35,6 +32,7 @@ describe('cap/samples - Messaging', ()=>{
|
|||||||
|
|
||||||
it ('should add review', async ()=>{
|
it ('should add review', async ()=>{
|
||||||
const review = { subject: "201", title: "Captivating", rating: ++N }
|
const review = { subject: "201", title: "Captivating", rating: ++N }
|
||||||
|
cds._debug = 1
|
||||||
const response = await srv.create ('Reviews') .entries (review)
|
const response = await srv.create ('Reviews') .entries (review)
|
||||||
expect (response) .to.containSubset (review)
|
expect (response) .to.containSubset (review)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,47 +4,6 @@ describe('cap/samples - Bookshop APIs', () => {
|
|||||||
const { GET, expect, axios } = cds.test ('@capire/bookshop')
|
const { GET, expect, axios } = cds.test ('@capire/bookshop')
|
||||||
axios.defaults.auth = { username: 'alice', password: 'admin' }
|
axios.defaults.auth = { username: 'alice', password: 'admin' }
|
||||||
|
|
||||||
// Genres
|
|
||||||
const Drama = {
|
|
||||||
"name": "Drama",
|
|
||||||
"descr": null,
|
|
||||||
"ID": 11,
|
|
||||||
"parent_ID": 10
|
|
||||||
}
|
|
||||||
const Mystery = {
|
|
||||||
"name": "Mystery",
|
|
||||||
"descr": null,
|
|
||||||
"ID": 16,
|
|
||||||
"parent_ID": 10
|
|
||||||
}
|
|
||||||
const Fantasy = {
|
|
||||||
"name": "Fantasy",
|
|
||||||
"descr": null,
|
|
||||||
"ID": 13,
|
|
||||||
"parent_ID": 10
|
|
||||||
}
|
|
||||||
|
|
||||||
// Currencies
|
|
||||||
const GBP = {
|
|
||||||
"name": "British Pound",
|
|
||||||
"descr": null,
|
|
||||||
"code": "GBP",
|
|
||||||
"symbol": "£"
|
|
||||||
}
|
|
||||||
const USD = {
|
|
||||||
"name": "US Dollar",
|
|
||||||
"descr": null,
|
|
||||||
"code": "USD",
|
|
||||||
"symbol": "$"
|
|
||||||
}
|
|
||||||
const JPY = {
|
|
||||||
"name": "Yen",
|
|
||||||
"descr": null,
|
|
||||||
"code": "JPY",
|
|
||||||
"symbol": "¥"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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`
|
||||||
expect(status).to.equal(200)
|
expect(status).to.equal(200)
|
||||||
@@ -57,12 +16,15 @@ describe('cap/samples - Bookshop APIs', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('serves ListOfBooks?$expand=genre,currency', async () => {
|
it('serves ListOfBooks?$expand=genre,currency', async () => {
|
||||||
|
const Mystery = { ID: 16, name: 'Mystery', descr: null, parent_ID: 10 }
|
||||||
|
const Romance = { ID: 15, name: 'Romance', descr: null, parent_ID: 10 }
|
||||||
|
const USD = { code: 'USD', name: 'US Dollar', descr: null, symbol: '$' }
|
||||||
const { data } = await GET `/browse/ListOfBooks ${{
|
const { data } = await GET `/browse/ListOfBooks ${{
|
||||||
params: { $search: 'Po', $select: `title,author`, $expand:`genre,currency` },
|
params: { $search: 'Po', $select: `title,author`, $expand:`genre,currency` },
|
||||||
}}`
|
}}`
|
||||||
expect(data.value).to.eql([
|
expect(data.value).to.eql([
|
||||||
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe', genre:Mystery, currency:USD },
|
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe', genre:Mystery, currency:USD },
|
||||||
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe', genre:Mystery, currency:USD },
|
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe', genre:Romance, currency:USD },
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -126,14 +88,10 @@ describe('cap/samples - Bookshop APIs', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('serves user info', async () => {
|
it('serves user info', async () => {
|
||||||
{
|
const { data: alice } = await GET `/user/me`
|
||||||
const { data } = await GET (`/user/me`)
|
expect(alice).to.containSubset({ id: 'alice', locale:'en' })
|
||||||
expect(data).to.containSubset({ id: 'alice', locale:'en', tenant: null })
|
const { data: joe } = await GET (`/user/me`, {auth: { username: 'joe' }})
|
||||||
}
|
expect(joe).to.containSubset({ id: 'joe', locale:'en' })
|
||||||
{
|
|
||||||
const { data } = await GET (`/user/me`, {auth: { username: 'joe' }})
|
|
||||||
expect(data).to.containSubset({ id: 'joe', locale:'en', tenant: null })
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
|
|
||||||
const cds = require('@sap/cds/lib')
|
|
||||||
const { fork } = require('child_process')
|
|
||||||
const { resolve } = require('path')
|
|
||||||
const verbose = process.env.CDS_TEST_VERBOSE
|
|
||||||
|
|
||||||
describe('cap/samples - Local NPM registry', () => {
|
|
||||||
|
|
||||||
const { expect } = cds.test
|
|
||||||
// ||true
|
|
||||||
|
|
||||||
let registry
|
|
||||||
let axios
|
|
||||||
const cwd = resolve(__dirname, '..')
|
|
||||||
|
|
||||||
before(async ()=> {
|
|
||||||
const env = Object.assign(process.env, {PORT:'0'})
|
|
||||||
const res = await exec (resolve(cwd, '.registry/server.js'), {cwd, stdio: 'pipe', env})
|
|
||||||
registry = res.cp
|
|
||||||
axios = require('axios').default.create ({ baseURL: res.url, validateStatus: (status)=>status<500 })
|
|
||||||
})
|
|
||||||
|
|
||||||
after(() => { registry.kill() })
|
|
||||||
|
|
||||||
for (const mod of ['bookshop', 'data-viewer', 'fiori','orders','reviews']) {
|
|
||||||
it(`should serve ${mod}`, async () => {
|
|
||||||
const resp = await axios.get(`/@capire/${mod}`)
|
|
||||||
expect(resp.data).to.containSubset({name: `@capire/${mod}`, versions:{}})
|
|
||||||
const versions = Object.values(resp.data.versions)
|
|
||||||
await axios.get(versions[0].dist.tarball)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
it(`should return 404 for unknown packages`, async () => {
|
|
||||||
let resp = await axios.get(`/@capire/foo`)
|
|
||||||
expect(resp.status).to.equal(404)
|
|
||||||
resp = await axios.get(`/foo`)
|
|
||||||
expect(resp.status).to.equal(404)
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
function exec (script, opts) {
|
|
||||||
return new Promise((resolve, reject)=> {
|
|
||||||
const cp = fork (script, [], opts)
|
|
||||||
.on('error', err => reject(new Error(err)))
|
|
||||||
cp.stdout.on('data', chunk => {
|
|
||||||
if (verbose) console.log(chunk.toString())
|
|
||||||
if (chunk.toString().match(/listening.*(http:.*:\d+)/i)) {
|
|
||||||
resolve({cp, url:RegExp.$1})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
cp.stderr.on('data', chunk => {
|
|
||||||
if (verbose) console.error(chunk.toString())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user