Compare commits
23 Commits
using-upse
...
fiori-ext
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c91e6802ac | ||
|
|
fdd1d86ac7 | ||
|
|
f50afdbd69 | ||
|
|
4bdad98165 | ||
|
|
5289f1a8fd | ||
|
|
a17458b572 | ||
|
|
54db241e28 | ||
|
|
8bcc91aa23 | ||
|
|
780a81229d | ||
|
|
0a17fbcb30 | ||
|
|
7819ad0bad | ||
|
|
06b96d0726 | ||
|
|
07a6540477 | ||
|
|
69403de663 | ||
|
|
a5b54b53cf | ||
|
|
c6b8cbfd0e | ||
|
|
21be9d1bbf | ||
|
|
dff3dd4b89 | ||
|
|
66007e2952 | ||
|
|
1e78d115cc | ||
|
|
5bda368169 | ||
|
|
ea989d8496 | ||
|
|
692ea3ddd2 |
@@ -13,7 +13,6 @@
|
|||||||
"globals": {
|
"globals": {
|
||||||
"SELECT": true,
|
"SELECT": true,
|
||||||
"INSERT": true,
|
"INSERT": true,
|
||||||
"UPSERT": true,
|
|
||||||
"UPDATE": true,
|
"UPDATE": true,
|
||||||
"DELETE": true,
|
"DELETE": true,
|
||||||
"CREATE": true,
|
"CREATE": true,
|
||||||
|
|||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: This channel is CLOSED.
|
|
||||||
about: Use SAP community instead
|
|
||||||
url: https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce
|
|
||||||
10
.github/ISSUE_TEMPLATE/question--feedback-or-bug-.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/question--feedback-or-bug-.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
name: This channel is CLOSED.
|
||||||
|
about: Use our community at https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Please use our community on https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce
|
||||||
@@ -1,27 +1,34 @@
|
|||||||
const { exec } = require ('child_process')
|
const { exec, execSync } = require ('child_process')
|
||||||
const isWin = process.platform === 'win32'
|
const isWin = process.platform === 'win32'
|
||||||
const express = require ('express')
|
const express = require ('express')
|
||||||
const fs = require ('fs')
|
const fs = require ('fs')
|
||||||
|
const { dirname, relative } = require('path')
|
||||||
|
const axios = require('axios')
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
const { PORT=4444 } = process.env
|
|
||||||
const [,,port=PORT,scope='@capire'] = process.argv
|
|
||||||
const cwd = __dirname
|
const cwd = __dirname
|
||||||
|
const port=process.env.PORT || 4444
|
||||||
|
let scopes = process.argv.filter(a => a.startsWith('@'))
|
||||||
|
if (!scopes.length) scopes = ['@capire']
|
||||||
|
|
||||||
// clean up on start (exit handler might not complete on Windows)
|
// clean up on start (exit handler might not complete on Windows)
|
||||||
exec(isWin ? 'del *.tgz' : 'rm *.tgz', {cwd})
|
exec(isWin ? 'del *.tgz' : 'rm *.tgz', {cwd})
|
||||||
|
|
||||||
|
|
||||||
app.use('/-/:tarball', (req,res,next) => {
|
app.use('/-/:tarball', async (req,res,next) => {
|
||||||
console.debug ('GET', req.params)
|
console.debug ('GET', req.params)
|
||||||
try {
|
try {
|
||||||
const { tarball } = req.params
|
const { tarball } = req.params
|
||||||
const pkgFull = tarball.substring(0, tarball.lastIndexOf('-'))
|
const pkgFull = tarball.substring(0, tarball.lastIndexOf('-'))
|
||||||
const [, pkg ] = /^\w+-(.+)/.exec(pkgFull)
|
const scope = '@'+pkgFull.substring(0, pkgFull.indexOf('-'))
|
||||||
|
const pkg = pkgFull.substring(pkgFull.indexOf('-')+1)
|
||||||
fs.lstat(tarball,(err => {
|
fs.lstat(tarball,(err => {
|
||||||
if (err) console.debug (`npm pack ../${pkg}`)
|
if (err) { // no tgz yet
|
||||||
if (err) exec(`npm pack ../${pkg}`,{cwd},next)
|
const loc = dirname(require.resolve(`${scope}/${pkg}/package.json`))
|
||||||
else next()
|
console.debug (`npm pack ${relative(cwd, loc)}`)
|
||||||
|
exec(`npm pack ${loc}`,{cwd},next)
|
||||||
|
}
|
||||||
|
else next() //> express.static below
|
||||||
}))
|
}))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@@ -31,14 +38,20 @@ app.use('/-/:tarball', (req,res,next) => {
|
|||||||
|
|
||||||
app.use('/-', express.static(__dirname))
|
app.use('/-', express.static(__dirname))
|
||||||
|
|
||||||
app.get('/*', (req,res)=>{
|
app.get('/*', async (req,res)=>{
|
||||||
const urlRegex = /^\/(@[\w-]+)\/(.+)/
|
const urlRegex = /^\/(@[\w-]+)\/(.+)/
|
||||||
const url = decodeURIComponent(req.url)
|
const url = decodeURIComponent(req.url)
|
||||||
console.debug ('GET',url)
|
console.debug ('GET',url)
|
||||||
try {
|
try {
|
||||||
if (!urlRegex.test(url)) return res.sendStatus(404)
|
if (!urlRegex.test(url)) return res.sendStatus(404)
|
||||||
const [, scpe, pkg ] = urlRegex.exec(url)
|
const [, scpe, pkg ] = urlRegex.exec(url)
|
||||||
const package = require (`${scpe}/${pkg}/package.json`)
|
const packageName = `${scpe}/${pkg}`
|
||||||
|
|
||||||
|
// delegate to default registry for @sap/non-cds packages
|
||||||
|
if (scpe === ('@sap') && !packageName.startsWith('@sap/cds')) {
|
||||||
|
return forward(req, res)
|
||||||
|
}
|
||||||
|
const package = require (`${packageName}/package.json`)
|
||||||
const tarball = `${scpe.slice(1)}-${pkg}-${package.version}.tgz`
|
const tarball = `${scpe.slice(1)}-${pkg}-${package.version}.tgz`
|
||||||
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
|
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
|
||||||
res.json({
|
res.json({
|
||||||
@@ -53,6 +66,8 @@ app.get('/*', (req,res)=>{
|
|||||||
"dist": {
|
"dist": {
|
||||||
"tarball": `${server.url}/-/${tarball}`
|
"tarball": `${server.url}/-/${tarball}`
|
||||||
},
|
},
|
||||||
|
dependencies: package.dependencies,
|
||||||
|
devDependencies: package.devDependencies
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -64,15 +79,32 @@ app.get('/*', (req,res)=>{
|
|||||||
|
|
||||||
const server = app.listen(port, ()=>{
|
const server = app.listen(port, ()=>{
|
||||||
const url = server.url = `http://localhost:${server.address().port}`
|
const url = server.url = `http://localhost:${server.address().port}`
|
||||||
console.log (`npm set ${scope}:registry=${url}`)
|
|
||||||
exec(`npm set ${scope}:registry=${url}`)
|
for (const scope of scopes) {
|
||||||
console.log (`${scope} registry listening on ${url}`)
|
console.log (`npm set ${scope}:registry=${url}`)
|
||||||
|
execSync(`npm set ${scope}:registry=${url}`)
|
||||||
|
}
|
||||||
|
console.log (`registry listening on ${url}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const _exit = ()=>{
|
const _exit = ()=>{
|
||||||
server.close()
|
server.close()
|
||||||
exec(`npm conf rm "${scope}:registry"`, ()=> { process.exit() })
|
for (const scope of scopes) {
|
||||||
|
execSync(`npm conf rm "${scope}:registry"`)
|
||||||
|
}
|
||||||
|
process.exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function forward(req, res) {
|
||||||
|
try {
|
||||||
|
const url = `https://registry.npmjs.org${req.url}`
|
||||||
|
const resAxios = await axios.get(url)
|
||||||
|
console.debug('->', decodeURI(url), resAxios.status)
|
||||||
|
return res.json(resAxios.data)
|
||||||
|
} catch (e) {
|
||||||
|
return res.sendStatus(e.response.status)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on ('SIGTERM',_exit)
|
process.on ('SIGTERM',_exit)
|
||||||
|
|||||||
@@ -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;15
|
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
|
||||||
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,9 +4,15 @@
|
|||||||
* currencies, if not obtained through @capire/common.
|
* currencies, if not obtained through @capire/common.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = async ()=>{
|
module.exports = async (db)=>{
|
||||||
|
|
||||||
await UPSERT.into ('sap.common.Currencies') .columns (
|
const has_common = db.model.definitions['sap.common.Currencies'].elements.numcode
|
||||||
|
if (has_common) return
|
||||||
|
|
||||||
|
const already_filled = await db.exists('sap.common.Currencies',{code:'EUR'})
|
||||||
|
if (already_filled) return
|
||||||
|
|
||||||
|
await INSERT.into ('sap.common.Currencies') .columns (
|
||||||
'code','symbol','name'
|
'code','symbol','name'
|
||||||
) .rows (
|
) .rows (
|
||||||
[ 'EUR','€','Euro' ],
|
[ 'EUR','€','Euro' ],
|
||||||
|
|||||||
@@ -26,8 +26,7 @@
|
|||||||
"[production]": { "kind": "enterprise-messaging" }
|
"[production]": { "kind": "enterprise-messaging" }
|
||||||
},
|
},
|
||||||
"db": {
|
"db": {
|
||||||
"kind": "sql",
|
"kind": "sql"
|
||||||
"schema_evolution": "auto"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"log": { "service": true }
|
"log": { "service": true }
|
||||||
|
|||||||
@@ -12,11 +12,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
fiori-ext/.vscode/settings.json
vendored
Normal file
11
fiori-ext/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"eslint.validate": [
|
||||||
|
"cds",
|
||||||
|
"csn",
|
||||||
|
"csv",
|
||||||
|
"csv",
|
||||||
|
"csv (semicolon)",
|
||||||
|
"tsv",
|
||||||
|
"tab"
|
||||||
|
]
|
||||||
|
}
|
||||||
21
fiori-ext/README.md
Normal file
21
fiori-ext/README.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Welcome to your Extension Project for the CAP Bookshop Fiori App
|
||||||
|
|
||||||
|
It contains these folders and files, following our recommended project layout:
|
||||||
|
|
||||||
|
| File or Folder | Purpose |
|
||||||
|
|----------------|------------------------------------|
|
||||||
|
| `app/` | content for UI frontends goes here |
|
||||||
|
| `test/` | contet for local tests |
|
||||||
|
| `package.json` | project metadata and configuration |
|
||||||
|
| `readme.md` | this getting started guide |
|
||||||
|
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- Runs `cds login ...`
|
||||||
|
- Runs `cds pull ...`
|
||||||
|
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
Learn more at https://cap.cloud.sap/docs/get-started/.
|
||||||
25
fiori-ext/app/extension.cds
Normal file
25
fiori-ext/app/extension.cds
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using { OrdersService, sap, sap.capire.orders.Orders } from '@capire/fiori';
|
||||||
|
namespace x_bookshop.extension;
|
||||||
|
|
||||||
|
// Adding 2 new fields for Orders
|
||||||
|
extend Orders with {
|
||||||
|
x_priority : String @assert.range enum {high; medium; low} default 'medium' ;
|
||||||
|
x_salesRegion : Association to SalesRegion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Value Help for x_salesRegion */
|
||||||
|
entity SalesRegion : sap.common.CodeList {
|
||||||
|
key code : String(11);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- UI ---
|
||||||
|
|
||||||
|
annotate Orders:x_priority with @title : 'Priority';
|
||||||
|
annotate SalesRegion:name with @title : 'Sales Region';
|
||||||
|
|
||||||
|
annotate OrdersService.Orders with @UI.LineItem : [
|
||||||
|
... up to { Value: OrderNo },
|
||||||
|
{ Value : x_priority },
|
||||||
|
{ Value : x_salesRegion.name },
|
||||||
|
...
|
||||||
|
];
|
||||||
49
fiori-ext/package.json
Normal file
49
fiori-ext/package.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "@capire/fiori-ext",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A simple CAP project.",
|
||||||
|
"repository": "<Add your repository here>",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@sap/cds": "^6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"sqlite3": "^5.0.4",
|
||||||
|
"@sap/eslint-plugin-cds": "^2.5.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "cds run"
|
||||||
|
},
|
||||||
|
"cds": {
|
||||||
|
"extends": "@capire/fiori"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@sap/cds/recommended"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"es2020": true,
|
||||||
|
"node": true,
|
||||||
|
"jest": true,
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"SELECT": true,
|
||||||
|
"INSERT": true,
|
||||||
|
"UPDATE": true,
|
||||||
|
"DELETE": true,
|
||||||
|
"CREATE": true,
|
||||||
|
"DROP": true,
|
||||||
|
"CDL": true,
|
||||||
|
"CQL": true,
|
||||||
|
"CXL": true,
|
||||||
|
"cds": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-console": "off",
|
||||||
|
"require-atomic-updates": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
fiori-ext/test/data/sap.capire.orders-Orders.csv
Normal file
3
fiori-ext/test/data/sap.capire.orders-Orders.csv
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ID;createdAt;createdBy;buyer;OrderNo;currency_code;Z_priority;Z_SalesRegion_regionCode
|
||||||
|
7e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-01-31;john.doe@test.com;john.doe@test.com;1;EUR;high;AMER
|
||||||
|
64e718c9-ff99-47f1-8ca3-950c850777d4;2019-01-30;jane.doe@test.com;jane.doe@test.com;2;EUR;low;APJ
|
||||||
|
4
fiori-ext/test/data/x_bookshop.extension-SalesRegion.csv
Normal file
4
fiori-ext/test/data/x_bookshop.extension-SalesRegion.csv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
code;name;descr
|
||||||
|
AMER;Americas;North, Central and South America
|
||||||
|
EMEA;Europe, the Middle East and Africa;Europe, the Middle East and Africa
|
||||||
|
APJ;Asia Pacific and Japan;Asia Pacific and Japan
|
||||||
|
@@ -6,33 +6,16 @@ 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
|
||||||
|
|||||||
12
fiori/app/_router/package.json
Normal file
12
fiori/app/_router/package.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "approuter",
|
||||||
|
"dependencies": {
|
||||||
|
"@sap/approuter": "^11.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^16"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node node_modules/@sap/approuter/approuter.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
23
fiori/app/_router/xs-app.json
Normal file
23
fiori/app/_router/xs-app.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"authenticationMethod": "route",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"source": "^/app/(.*)$",
|
||||||
|
"target": "$1",
|
||||||
|
"localDir": ".",
|
||||||
|
"authenticationType": "xsuaa",
|
||||||
|
"cacheControl": "no-cache, no-store, must-revalidate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "^/-/cds/.*",
|
||||||
|
"destination": "mtx-api",
|
||||||
|
"authenticationType": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "^/(.*)$",
|
||||||
|
"target": "$1",
|
||||||
|
"destination": "srv-api",
|
||||||
|
"authenticationType": "xsuaa"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -43,10 +43,5 @@ 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,11 +62,6 @@ 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 @(
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ annotate CatalogService.Books with @(UI : {
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Books List Page
|
// Books Object Page
|
||||||
//
|
//
|
||||||
annotate CatalogService.Books with @(UI : {
|
annotate CatalogService.Books with @(UI : {
|
||||||
SelectionFields : [
|
SelectionFields : [
|
||||||
@@ -52,6 +52,9 @@ annotate CatalogService.Books with @(UI : {
|
|||||||
},
|
},
|
||||||
{Value : genre.name},
|
{Value : genre.name},
|
||||||
{Value : price},
|
{Value : price},
|
||||||
{Value : currency.symbol},
|
{
|
||||||
|
Value : currency.symbol,
|
||||||
|
Label : ' '
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}, );
|
}, );
|
||||||
|
|||||||
@@ -4,53 +4,57 @@
|
|||||||
|
|
||||||
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: author.ID, Label: '{i18n>Author}' },
|
Value : ID,
|
||||||
{ Value: genre.name },
|
Label : '{i18n>Title}'
|
||||||
{ Value: stock },
|
},
|
||||||
{ Value: price },
|
{
|
||||||
{ Value: currency.symbol },
|
Value : author.ID,
|
||||||
]
|
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}
|
||||||
}, });
|
}, });
|
||||||
|
|
||||||
|
|
||||||
@@ -59,14 +63,19 @@ 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: { Text: genre.name, TextArrangement: #TextOnly };
|
genre @title : '{i18n>Genre}' @Common : {
|
||||||
author @title: '{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
|
Text : genre.name,
|
||||||
price @title: '{i18n>Price}' @Measures.ISOCurrency : currency_code;
|
TextArrangement : #TextOnly
|
||||||
stock @title: '{i18n>Stock}';
|
};
|
||||||
descr @title: '{i18n>Description}' @UI.MultiLineText;
|
author @title : '{i18n>Author}' @Common : {
|
||||||
image @title: '{i18n>Image}';
|
Text : author.name,
|
||||||
|
TextArrangement : #TextOnly
|
||||||
|
};
|
||||||
|
price @title : '{i18n>Price}' @Measures.ISOCurrency : currency_code;
|
||||||
|
stock @title : '{i18n>Stock}';
|
||||||
|
descr @UI.MultiLineText;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -74,40 +83,36 @@ 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'
|
||||||
}, ],
|
}, ],
|
||||||
});
|
});
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -115,8 +120,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}';
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -124,24 +129,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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -149,16 +154,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'
|
||||||
}, ],
|
}, ],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -167,12 +172,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}';
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -180,18 +185,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},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -199,22 +204,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}
|
||||||
]},
|
]},
|
||||||
});
|
});
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -222,19 +227,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},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -242,35 +247,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',
|
{
|
||||||
Label : '{i18n>Extended}',
|
$Type : 'UI.ReferenceFacet',
|
||||||
Target : '@UI.FieldGroup#Extended'
|
Label : '{i18n>Details}',
|
||||||
},
|
Target : '@UI.FieldGroup#Details'
|
||||||
],
|
},
|
||||||
FieldGroup #Details : {Data : [
|
{
|
||||||
{ Value: name },
|
$Type : 'UI.ReferenceFacet',
|
||||||
{ Value: symbol },
|
Label : '{i18n>Extended}',
|
||||||
{ Value: code },
|
Target : '@UI.FieldGroup#Extended'
|
||||||
{ Value: descr }
|
},
|
||||||
]},
|
],
|
||||||
FieldGroup #Extended : {Data : [
|
FieldGroup #Details : {Data : [
|
||||||
{ Value: numcode },
|
{Value : name},
|
||||||
{ Value: minor },
|
{Value : symbol},
|
||||||
{ Value: exponent }
|
{Value : code},
|
||||||
]},
|
{Value : descr}
|
||||||
|
]},
|
||||||
|
FieldGroup #Extended : {Data : [
|
||||||
|
{Value : numcode},
|
||||||
|
{Value : minor},
|
||||||
|
{Value : exponent}
|
||||||
|
]},
|
||||||
});
|
});
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -278,7 +283,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}';
|
||||||
}
|
}
|
||||||
|
|||||||
24
fiori/app/xs-app.json
Normal file
24
fiori/app/xs-app.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"authenticationMethod": "route",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"source": "^/-/cds/.*",
|
||||||
|
"destination": "mtx-api",
|
||||||
|
"authenticationType": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "^/app/(.*)$",
|
||||||
|
"target": "$1",
|
||||||
|
"localDir": ".",
|
||||||
|
"authenticationType": "xsuaa",
|
||||||
|
"cacheControl": "no-cache, no-store, must-revalidate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "^/(.*)$",
|
||||||
|
"target": "$1",
|
||||||
|
"destination": "srv-api",
|
||||||
|
"authenticationType": "xsuaa",
|
||||||
|
"csrfProtection": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
5
fiori/db/data/sap.capire.bookshop-Books_texts.csv
Normal file
5
fiori/db/data/sap.capire.bookshop-Books_texts.csv
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ID_texts;ID;locale;title;descr
|
||||||
|
201_de;201;de;Sturmhöhe;Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman der englischen Schriftstellerin Emily Brontë (1818–1848). Der 1847 unter dem Pseudonym Ellis Bell veröffentlichte Roman wurde vom viktorianischen Publikum weitgehend abgelehnt, heute gilt er als ein Klassiker der britischen Romanliteratur des 19. Jahrhunderts.
|
||||||
|
201_fr;201;fr;Les Hauts de Hurlevent;Les Hauts de Hurlevent (titre original : Wuthering Heights), parfois orthographié Les Hauts de Hurle-Vent, est l'unique roman d'Emily Brontë, publié pour la première fois en 1847 sous le pseudonyme d’Ellis Bell. Loin d'être un récit moralisateur, Emily Brontë achève néanmoins le roman dans une atmosphère sereine, suggérant le triomphe de la paix et du Bien sur la vengeance et le Mal.
|
||||||
|
207_de;207;de;Jane Eyre;Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography), erstmals erschienen im Jahr 1847 unter dem Pseudonym Currer Bell, ist der erste veröffentlichte Roman der britischen Autorin Charlotte Brontë und ein Klassiker der viktorianischen Romanliteratur des 19. Jahrhunderts. Der Roman erzählt in Form einer Ich-Erzählung die Lebensgeschichte von Jane Eyre (ausgesprochen /ˌdʒeɪn ˈɛə/), die nach einer schweren Kindheit eine Stelle als Gouvernante annimmt und sich in ihren Arbeitgeber verliebt, jedoch immer wieder um ihre Freiheit und Selbstbestimmung kämpfen muss. Als klein, dünn, blass, stets schlicht dunkel gekleidet und mit strengem Mittelscheitel beschrieben, gilt die Heldin des Romans Jane Eyre nicht zuletzt aufgrund der Kino- und Fernsehversionen der melodramatischen Romanvorlage als die bekannteste englische Gouvernante der Literaturgeschichte
|
||||||
|
207_fr;252;de;Eleonora;“Eleonora” ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841 erstveröffentlicht. In ihr geht es um das Paradox der Treue in der Treulosigkeit.
|
||||||
|
@@ -2,9 +2,14 @@
|
|||||||
// Add Author.age and .lifetime with a DB-specific function
|
// Add Author.age and .lifetime with a DB-specific function
|
||||||
//
|
//
|
||||||
|
|
||||||
using { AdminService } from '@capire/bookshop';
|
using { AdminService, sap.common } from '@capire/bookshop';
|
||||||
|
|
||||||
extend projection AdminService.Authors with {
|
extend projection AdminService.Authors with {
|
||||||
YEARS_BETWEEN(dateOfBirth, dateOfDeath) as age: Integer,
|
YEARS_BETWEEN(dateOfBirth, dateOfDeath) as age: Integer,
|
||||||
YEAR(dateOfBirth) || ' – ' || YEAR(dateOfDeath) as lifetime : String
|
YEAR(dateOfBirth) || ' – ' || YEAR(dateOfDeath) as lifetime : String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Workaround: include Countries table because csv files point to it
|
||||||
|
// TODO fix by ignoring hdbtabledata generation for unused entities
|
||||||
|
annotate common.Countries with @cds.persistence.skip : false;
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
using from './db/common';
|
|
||||||
97
fiori/mta.yaml
Normal file
97
fiori/mta.yaml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
_schema-version: '3.1'
|
||||||
|
ID: capire.fiori
|
||||||
|
version: 1.0.0
|
||||||
|
description: "fiori"
|
||||||
|
parameters:
|
||||||
|
enable-parallel-deployments: true
|
||||||
|
build-parameters:
|
||||||
|
before-all:
|
||||||
|
- builder: custom
|
||||||
|
commands:
|
||||||
|
- npx -p @sap/cds-dk cds build --production
|
||||||
|
|
||||||
|
modules:
|
||||||
|
- name: fiori-srv
|
||||||
|
type: nodejs
|
||||||
|
path: gen/srv
|
||||||
|
parameters:
|
||||||
|
buildpack: nodejs_buildpack
|
||||||
|
build-parameters:
|
||||||
|
builder: npm
|
||||||
|
provides:
|
||||||
|
- name: srv-api # required by consumers of CAP services (e.g. approuter)
|
||||||
|
properties:
|
||||||
|
srv-url: ${default-url}
|
||||||
|
- name: mtx-api # potentially required by approuter
|
||||||
|
properties:
|
||||||
|
mtx-url: ${default-url}
|
||||||
|
requires:
|
||||||
|
- name: fiori-db
|
||||||
|
- name: fiori-registry
|
||||||
|
- name: fiori-auth
|
||||||
|
- name: app-api
|
||||||
|
properties:
|
||||||
|
SUBSCRIPTION_URL: ~{app-protocol}://\${tenant_subdomain}-~{app-uri}
|
||||||
|
|
||||||
|
- name: fiori
|
||||||
|
type: approuter.nodejs
|
||||||
|
path: app/_router # from cds.env.folders. Consider also cds.env.build.target -> gen/app
|
||||||
|
parameters:
|
||||||
|
keep-existing-routes: true
|
||||||
|
disk-quota: 256M
|
||||||
|
memory: 256M
|
||||||
|
properties:
|
||||||
|
TENANT_HOST_PATTERN: "^(.*)-${default-uri}"
|
||||||
|
requires:
|
||||||
|
- name: srv-api
|
||||||
|
group: destinations
|
||||||
|
properties:
|
||||||
|
name: srv-api # must be used in xs-app.json as well
|
||||||
|
url: ~{srv-url}
|
||||||
|
forwardAuthToken: true
|
||||||
|
- name: mtx-api
|
||||||
|
group: destinations
|
||||||
|
properties:
|
||||||
|
name: mtx-api # must be used in xs-app.json as well
|
||||||
|
url: ~{mtx-url}
|
||||||
|
- name: fiori-auth
|
||||||
|
provides:
|
||||||
|
- name: app-api
|
||||||
|
properties:
|
||||||
|
app-protocol: ${protocol}
|
||||||
|
app-uri: ${default-uri}
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- name: fiori-db
|
||||||
|
type: org.cloudfoundry.managed-service
|
||||||
|
parameters:
|
||||||
|
service: service-manager
|
||||||
|
service-plan: container
|
||||||
|
- name: fiori-registry
|
||||||
|
type: org.cloudfoundry.managed-service
|
||||||
|
requires:
|
||||||
|
- name: mtx-api
|
||||||
|
parameters:
|
||||||
|
service: saas-registry
|
||||||
|
service-plan: application
|
||||||
|
config:
|
||||||
|
xsappname: fiori-${org}-${space}
|
||||||
|
appName: fiori-${org}-${space}
|
||||||
|
displayName: fiori
|
||||||
|
description: A simple CAP project.
|
||||||
|
category: 'Category'
|
||||||
|
appUrls:
|
||||||
|
getDependencies: ~{mtx-api/mtx-url}/-/cds/saas-provisioning/dependencies
|
||||||
|
onSubscription: ~{mtx-api/mtx-url}/-/cds/saas-provisioning/tenant/{tenantId}
|
||||||
|
onSubscriptionAsync: false
|
||||||
|
onUnSubscriptionAsync: false
|
||||||
|
callbackTimeoutMillis: 300000
|
||||||
|
- name: fiori-auth
|
||||||
|
type: org.cloudfoundry.managed-service
|
||||||
|
parameters:
|
||||||
|
service: xsuaa
|
||||||
|
service-plan: application
|
||||||
|
path: ./xs-security.json
|
||||||
|
config:
|
||||||
|
xsappname: fiori-${org}-${space}
|
||||||
|
tenant-mode: shared
|
||||||
@@ -4,15 +4,24 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capire/bookstore": "*",
|
"@capire/bookstore": "*",
|
||||||
"@sap/cds": ">=5",
|
"@sap/cds": ">=5",
|
||||||
|
"@sap/cds-mtxs": "^1",
|
||||||
"@sap/cds-odata-v2-adapter-proxy": "^1.9.0",
|
"@sap/cds-odata-v2-adapter-proxy": "^1.9.0",
|
||||||
|
"@sap/xssec": "^3",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"hdb": "^0.19.5",
|
||||||
"passport": ">=0.4.1"
|
"passport": ">=0.4.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cds run --in-memory?",
|
"start": "cds run --in-memory?",
|
||||||
"watch": "cds watch"
|
"watch": "cds watch"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^16"
|
||||||
|
},
|
||||||
"cds": {
|
"cds": {
|
||||||
|
"features": {
|
||||||
|
"deploy_data_onconflict": "replace"
|
||||||
|
},
|
||||||
"requires": {
|
"requires": {
|
||||||
"ReviewsService": {
|
"ReviewsService": {
|
||||||
"kind": "odata",
|
"kind": "odata",
|
||||||
@@ -34,8 +43,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"db": {
|
"db": {
|
||||||
"kind": "sql",
|
"kind": "sql-mt"
|
||||||
"schema_evolution": "auto"
|
|
||||||
},
|
},
|
||||||
"db-ext": {
|
"db-ext": {
|
||||||
"[development]": {
|
"[development]": {
|
||||||
@@ -44,10 +52,41 @@
|
|||||||
"[production]": {
|
"[production]": {
|
||||||
"model": "db/hana"
|
"model": "db/hana"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"multitenancy": true,
|
||||||
|
"toggles": true,
|
||||||
|
"extensibility": true,
|
||||||
|
"cds.xt.ExtensibilityService": {
|
||||||
|
"element-prefix": [
|
||||||
|
"x_"
|
||||||
|
],
|
||||||
|
"extension-allowlist": [
|
||||||
|
{
|
||||||
|
"for": [
|
||||||
|
"sap.capire.orders"
|
||||||
|
],
|
||||||
|
"kind": "entity",
|
||||||
|
"new-fields": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"for": [
|
||||||
|
"OrdersService"
|
||||||
|
],
|
||||||
|
"new-entities": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"[production]": {
|
||||||
|
"auth": {
|
||||||
|
"kind": "xsuaa"
|
||||||
|
},
|
||||||
|
"db": {
|
||||||
|
"kind": "hana-mt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"approuter": {
|
||||||
|
"kind": "cloudfoundry"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"hana": {
|
|
||||||
"deploy-format": "hdbtable"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,4 +5,11 @@ const proxy = require('@sap/cds-odata-v2-adapter-proxy')
|
|||||||
const proxyOpts = global.it ? { target:'auto' } : {} // for tests, set 'auto' to detect port dynamically
|
const proxyOpts = 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(proxyOpts)))
|
||||||
|
|
||||||
module.exports = require('@capire/bookstore/server.js')
|
module.exports = require('@capire/bookstore/server.js')
|
||||||
|
|
||||||
|
// For didactic reasons in capire, run below services embedded
|
||||||
|
// TODO find a better way to switch this
|
||||||
|
if (cds.requires.multitenancy) {
|
||||||
|
delete cds.env.requires.OrdersService
|
||||||
|
delete cds.env.requires.ReviewsService
|
||||||
|
}
|
||||||
1
fiori/srv/index.cds
Normal file
1
fiori/srv/index.cds
Normal file
@@ -0,0 +1 @@
|
|||||||
|
using from '@capire/bookstore';
|
||||||
51
fiori/xs-security.json
Normal file
51
fiori/xs-security.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.admin",
|
||||||
|
"description": "admin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.mtcallback",
|
||||||
|
"description": "Subscription via SaaS Registry",
|
||||||
|
"grant-as-authority-to-apps": [
|
||||||
|
"$XSAPPNAME(application,sap-provisioning,tenant-onboarding)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.cds.Subscriber",
|
||||||
|
"description": "Subscribe to applications"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.cds.ExtensionDeveloper",
|
||||||
|
"description": "Extend CAP applications via extension projects"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XSAPPNAME.cds.UIFlexDeveloper",
|
||||||
|
"description": "Extend CAP applications via UIFlex extensibility"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attributes": [],
|
||||||
|
"role-templates": [
|
||||||
|
{
|
||||||
|
"name": "admin",
|
||||||
|
"description": "admin",
|
||||||
|
"scope-references": [
|
||||||
|
"$XSAPPNAME.admin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ExtensionDeveloper",
|
||||||
|
"description": "Extension development including UIFlex extensibility",
|
||||||
|
"scope-references": [
|
||||||
|
"$XSAPPNAME.cds.ExtensionDeveloper",
|
||||||
|
"$XSAPPNAME.cds.UIFlexDeveloper"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"authorities-inheritance": false,
|
||||||
|
"authorities": [
|
||||||
|
"$XSAPPNAME.cds.Subscriber",
|
||||||
|
"$XSAPPNAME.cds.ExtensionDeveloper",
|
||||||
|
"$XSAPPNAME.cds.UIFlexDeveloper"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
module.exports = class say {
|
module.exports = class say {
|
||||||
hello(req) {
|
hello(req) { return `Hello ${req.data.to}!` }
|
||||||
let {to} = req.data
|
|
||||||
if (to === 'me') to = require('os').userInfo().username
|
|
||||||
return `Hello ${to}!`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title> cds.log </title>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/primitive-ui/dist/css/main.css">
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
|
|
||||||
<style>
|
|
||||||
select { border-color: transparent; padding: 4px 12px; margin: 0px; }
|
|
||||||
button { padding: 2px 11px; margin: 0px 4px; font: 90% italic; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="small-container" , style="margin-top: 70px;">
|
|
||||||
<div id='app'>
|
|
||||||
<h1> Log Levels </h1>
|
|
||||||
<input type="text" placeholder="Search by ID or Log Level..." @input="fetch">
|
|
||||||
<table id='loggers'>
|
|
||||||
<thead>
|
|
||||||
<th> Module ID </th>
|
|
||||||
<th> Log Level </th>
|
|
||||||
</thead>
|
|
||||||
<tr v-for="each in list">
|
|
||||||
<td>{{ each.id }}</td>
|
|
||||||
<td><select v-bind:id="each.id" v-model="each.level" @change="set">
|
|
||||||
<option>SILENT</option>
|
|
||||||
<option>ERROR</option>
|
|
||||||
<option>WARN</option>
|
|
||||||
<option>INFO</option>
|
|
||||||
<option>DEBUG</option>
|
|
||||||
<option>TRACE</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<h4>Log Format:</h4>
|
|
||||||
[ <button class="round-button" :class={'muted-button':!format.timestamp} @click="toggle_format" id="timestamp">Timestamp </button>
|
|
||||||
| <button class="round-button" :class={'muted-button':!format.level} @click="toggle_format" id="level">Log Level </button>
|
|
||||||
| <button class="round-button" :class={'muted-button':!format.tenant} @click="toggle_format" id="tenant">Tenant </button>
|
|
||||||
| <button class="round-button" :class={'muted-button':!format.reqid} @click="toggle_format" id="reqid">Request ID </button>
|
|
||||||
| <button class="round-button" :class={'muted-button':!format.id} @click="toggle_format" id="module">Logger ID </button>
|
|
||||||
] - <i>log message ...</i>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
axios.defaults.headers['Content-Type'] = 'application/json'
|
|
||||||
axios.defaults.baseURL = '/log'
|
|
||||||
const loggers = Vue.createApp({ el: '#app',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
format: { timestamp:false, level:false, tenant:false, reqid:false, id:true, },
|
|
||||||
list: [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetch (eve) {
|
|
||||||
this.list = (await axios.get (`/Loggers${
|
|
||||||
eve && eve.target.value ? `?$search=${eve.target.value}` : ''
|
|
||||||
}`)).data
|
|
||||||
},
|
|
||||||
async set (eve) {
|
|
||||||
const { id, value:level } = eve.target
|
|
||||||
await axios.put (`/Logger/${id}`, {id,level})
|
|
||||||
},
|
|
||||||
async toggle_format (eve) {
|
|
||||||
this.format[eve.target.id] = !this.format[eve.target.id]
|
|
||||||
await axios.post (`/format`, this.format)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).mount('#app')
|
|
||||||
loggers.fetch() // initially fill list of loggers
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@capire/loggers",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Simple sample on how to dynamically set cds.log levels and formats.",
|
|
||||||
"files": [
|
|
||||||
"app",
|
|
||||||
"srv"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"@sap/cds": ">=5.9",
|
|
||||||
"express": "^4.17.1"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "cds run",
|
|
||||||
"watch": "cds watch"
|
|
||||||
},
|
|
||||||
"cds": {
|
|
||||||
"requires": {
|
|
||||||
"db": "sql"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# Dynamically Set `cds.log` Levels and Formats
|
|
||||||
|
|
||||||
### Run
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cds watch
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test
|
|
||||||
|
|
||||||
Either using the UI through http://localhost:4004/loggers.html, or try the requests in `test/requests.http`
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
service Sue {
|
|
||||||
entity Dummy { key ID: UUID; title: String; }
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
@rest service LogService {
|
|
||||||
|
|
||||||
@readonly entity Loggers : Logger {};
|
|
||||||
entity Logger {
|
|
||||||
key id : String;
|
|
||||||
level : String;
|
|
||||||
}
|
|
||||||
|
|
||||||
action format (
|
|
||||||
timestamp : Boolean,
|
|
||||||
level : Boolean,
|
|
||||||
tenant : Boolean,
|
|
||||||
reqid : Boolean,
|
|
||||||
id : Boolean,
|
|
||||||
);
|
|
||||||
|
|
||||||
action debug (logger : String) returns Logger;
|
|
||||||
action reset (logger : String) returns Logger;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
const cds = require ('@sap/cds/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' ]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
http://localhost:4004/loggers.html
|
|
||||||
@body: = Content-Type: application/json\n\n
|
|
||||||
|
|
||||||
###
|
|
||||||
GET http://localhost:4004/log/Loggers
|
|
||||||
|
|
||||||
###
|
|
||||||
PUT http://localhost:4004/log/Logger/sqlite
|
|
||||||
{{body:}} { "level": "debug" }
|
|
||||||
|
|
||||||
###
|
|
||||||
POST http://localhost:4004/log/debug(logger='sqlite')
|
|
||||||
|
|
||||||
###
|
|
||||||
POST http://localhost:4004/log/reset(logger='sqlite')
|
|
||||||
|
|
||||||
### Dummy request to see sqlite debug output
|
|
||||||
GET http://localhost:4004/sue/Dummy
|
|
||||||
@@ -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 = Buffer.from(
|
const decodedMedia = new Buffer(
|
||||||
mediaObj.media.split(';base64,').pop(),
|
mediaObj.media.split(';base64,').pop(),
|
||||||
'base64'
|
'base64'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,24 +16,23 @@ using { OrdersService } from '../srv/orders-service';
|
|||||||
@odata.draft.enabled
|
@odata.draft.enabled
|
||||||
annotate OrdersService.Orders with @(
|
annotate OrdersService.Orders with @(
|
||||||
UI: {
|
UI: {
|
||||||
SelectionFields: [ createdBy ],
|
SelectionFields: [ createdAt, createdBy ],
|
||||||
LineItem: [
|
LineItem: [
|
||||||
{Value: OrderNo, Label:'{i18n>OrderNo}'},
|
{Value: OrderNo, Label:'OrderNo'},
|
||||||
{Value: buyer, Label:'{i18n>Customer}'},
|
{Value: buyer, Label:'Customer'},
|
||||||
{Value: currency.symbol, Label:'{i18n>Currency}'},
|
{Value: createdAt, Label:'Date'}
|
||||||
{Value: createdAt, Label:'{i18n>Date}'},
|
|
||||||
],
|
],
|
||||||
HeaderInfo: {
|
HeaderInfo: {
|
||||||
TypeName: '{i18n>Order}', TypeNamePlural: '{i18n>Orders}',
|
TypeName: 'Order', TypeNamePlural: 'Orders',
|
||||||
Title: {
|
Title: {
|
||||||
Label: '{i18n>OrderNo}', //A label is possible but it is not considered on the ObjectPage yet
|
Label: 'Order number ', //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:'{i18n>Customer}'},
|
{Value: createdBy, Label:'Customer'},
|
||||||
{Value: createdAt, Label:'{i18n>Date}'},
|
{Value: createdAt, Label:'Date'},
|
||||||
{Value: OrderNo },
|
{Value: OrderNo },
|
||||||
],
|
],
|
||||||
HeaderFacets: [
|
HeaderFacets: [
|
||||||
@@ -46,7 +45,7 @@ annotate OrdersService.Orders with @(
|
|||||||
],
|
],
|
||||||
FieldGroup#Details: {
|
FieldGroup#Details: {
|
||||||
Data: [
|
Data: [
|
||||||
{Value: currency.code, Label:'{i18n>Currency}'}
|
{Value: currency.code, Label:'Currency'}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
FieldGroup#Created: {
|
FieldGroup#Created: {
|
||||||
@@ -65,7 +64,6 @@ annotate OrdersService.Orders with @(
|
|||||||
) {
|
) {
|
||||||
createdAt @UI.HiddenFilter:false;
|
createdAt @UI.HiddenFilter:false;
|
||||||
createdBy @UI.HiddenFilter:false;
|
createdBy @UI.HiddenFilter:false;
|
||||||
ID @UI.Hidden;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -73,15 +71,15 @@ annotate OrdersService.Orders with @(
|
|||||||
annotate OrdersService.Orders.Items with @(
|
annotate OrdersService.Orders.Items with @(
|
||||||
UI: {
|
UI: {
|
||||||
LineItem: [
|
LineItem: [
|
||||||
{Value: product_ID, Label:'{i18n>ProductID}'},
|
{Value: product_ID, Label:'Product ID'},
|
||||||
{Value: title, Label:'{i18n>ProductTitle}'},
|
{Value: title, Label:'Product Title'},
|
||||||
{Value: price, Label:'{i18n>UnitPrice}'},
|
{Value: price, Label:'Unit Price'},
|
||||||
{Value: quantity, Label:'{i18n>Quantity}'},
|
{Value: quantity, Label:'Quantity'},
|
||||||
],
|
],
|
||||||
Identification: [ //Is the main field group
|
Identification: [ //Is the main field group
|
||||||
{Value: quantity, Label:'{i18n>Quantity}'},
|
{Value: quantity, Label:'Quantity'},
|
||||||
{Value: title, Label:'{i18n>Product}'},
|
{Value: title, Label:'Product'},
|
||||||
{Value: price, Label:'{i18n>UnitPrice}'},
|
{Value: price, Label:'Unit Price'},
|
||||||
],
|
],
|
||||||
Facets: [
|
Facets: [
|
||||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>OrderItems}', Target: '@UI.Identification'},
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>OrderItems}', Target: '@UI.Identification'},
|
||||||
@@ -91,7 +89,4 @@ 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: "CAP Sample App",
|
description: "... testing FE v42",
|
||||||
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 Management",
|
"title": "Order Books",
|
||||||
"description": "CAP Sample Application",
|
"description": "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(22) @title:'Order Number'; //> readable key
|
OrderNo : String @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;
|
||||||
|
|||||||
3420
package-lock.json
generated
3420
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -8,19 +8,10 @@
|
|||||||
"@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": "^1",
|
"axios": "^0",
|
||||||
"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",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
ID;subject;rating;reviewer;title;text
|
ID;subject;rating;reviewer;title;text
|
||||||
1223a37c-9dca-4c4c-a629-9dd93b947864;201;5;bob;Intriguing;Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
5de47328-2ad2-4449-bb8c-e4000586b687;201;5;bob;Intriguing;Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||||
ae275d4f-1fb5-491b-ba6e-79b339e1edef;201;4;bob;Fascinating;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id diam maecenas ultricies mi eget mauris pharetra et. Risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci eu. Nec sagittis aliquam malesuada bibendum arcu. Eu consequat ac felis donec. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Tortor posuere ac ut consequat semper viverra nam libero. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Scelerisque purus semper eget duis at tellus. Elementum tempus egestas sed sed risus pretium. Arcu dictum varius duis at. Amet luctus venenatis lectus magna fringilla urna. Eget velit aliquet sagittis id consectetur purus ut faucibus. Vitae auctor eu augue ut lectus. Fermentum iaculis eu non diam phasellus vestibulum.
|
f77ea7a8-01c8-469d-bf9e-80988758a2ee;201;4;bob;Fascinating;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id diam maecenas ultricies mi eget mauris pharetra et. Risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci eu. Nec sagittis aliquam malesuada bibendum arcu. Eu consequat ac felis donec. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Tortor posuere ac ut consequat semper viverra nam libero. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Scelerisque purus semper eget duis at tellus. Elementum tempus egestas sed sed risus pretium. Arcu dictum varius duis at. Amet luctus venenatis lectus magna fringilla urna. Eget velit aliquet sagittis id consectetur purus ut faucibus. Vitae auctor eu augue ut lectus. Fermentum iaculis eu non diam phasellus vestibulum.
|
||||||
3fffe4b7-d34f-4fd3-9aab-f609f41539cc;207;2;bob;What is this?;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Libero justo laoreet sit amet cursus sit amet dictum. Nunc faucibus a pellentesque sit. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Enim nunc faucibus a pellentesque. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Cras ornare arcu dui vivamus. Facilisi etiam dignissim diam quis enim lobortis. Et molestie ac feugiat sed. Urna neque viverra justo nec ultrices dui. Ullamcorper a lacus vestibulum sed arcu non. Volutpat ac tincidunt vitae semper quis. Dignissim sodales ut eu sem. Feugiat in fermentum posuere urna nec. At augue eget arcu dictum varius.
|
6b7ee8c9-0a18-4716-b333-eb95b7570f4e;207;2;bob;What is this?;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Libero justo laoreet sit amet cursus sit amet dictum. Nunc faucibus a pellentesque sit. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Enim nunc faucibus a pellentesque. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Cras ornare arcu dui vivamus. Facilisi etiam dignissim diam quis enim lobortis. Et molestie ac feugiat sed. Urna neque viverra justo nec ultrices dui. Ullamcorper a lacus vestibulum sed arcu non. Volutpat ac tincidunt vitae semper quis. Dignissim sodales ut eu sem. Feugiat in fermentum posuere urna nec. At augue eget arcu dictum varius.
|
||||||
71c037b3-767d-4f5d-9e90-90b894a2043a;251;3;bob;It's dark...;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Suscipit tellus mauris a diam. Velit aliquet sagittis id consectetur purus ut. Viverra adipiscing at in tellus integer. Vitae elementum curabitur vitae nunc. Mattis ullamcorper velit sed ullamcorper morbi. Diam quis enim lobortis scelerisque. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Semper auctor neque vitae tempus. Quis eleifend quam adipiscing vitae proin. Neque convallis a cras semper auctor neque vitae. Imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet consectetur adipiscing elit ut aliquam purus. Pretium quam vulputate dignissim suspendisse.
|
10403669-7e56-4668-bfb0-2071bbc947f3;251;3;bob;It's dark...;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Suscipit tellus mauris a diam. Velit aliquet sagittis id consectetur purus ut. Viverra adipiscing at in tellus integer. Vitae elementum curabitur vitae nunc. Mattis ullamcorper velit sed ullamcorper morbi. Diam quis enim lobortis scelerisque. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Semper auctor neque vitae tempus. Quis eleifend quam adipiscing vitae proin. Neque convallis a cras semper auctor neque vitae. Imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet consectetur adipiscing elit ut aliquam purus. Pretium quam vulputate dignissim suspendisse.
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"cds": {
|
|
||||||
"requires": {
|
|
||||||
"db": {
|
|
||||||
"kind": "sql",
|
|
||||||
"schema_evolution": "auto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,10 +17,10 @@ describe('cap/samples - Bookshop APIs', () => {
|
|||||||
"ID": 16,
|
"ID": 16,
|
||||||
"parent_ID": 10
|
"parent_ID": 10
|
||||||
}
|
}
|
||||||
const Romance = {
|
const Fantasy = {
|
||||||
"name": "Romance",
|
"name": "Fantasy",
|
||||||
"descr": null,
|
"descr": null,
|
||||||
"ID": 15,
|
"ID": 13,
|
||||||
"parent_ID": 10
|
"parent_ID": 10
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ describe('cap/samples - Bookshop APIs', () => {
|
|||||||
}}`
|
}}`
|
||||||
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:Romance, currency:USD },
|
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe', genre:Mystery, currency:USD },
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -128,11 +128,11 @@ describe('cap/samples - Bookshop APIs', () => {
|
|||||||
it('serves user info', async () => {
|
it('serves user info', async () => {
|
||||||
{
|
{
|
||||||
const { data } = await GET (`/user/me`)
|
const { data } = await GET (`/user/me`)
|
||||||
expect(data).to.containSubset({ id: 'alice', locale:'en' })
|
expect(data).to.containSubset({ id: 'alice', locale:'en', tenant: null })
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const { data } = await GET (`/user/me`, {auth: { username: 'joe' }})
|
const { data } = await GET (`/user/me`, {auth: { username: 'joe' }})
|
||||||
expect(data).to.containSubset({ id: 'joe', locale:'en' })
|
expect(data).to.containSubset({ id: 'joe', locale:'en', tenant: null })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ describe('cap/samples - Local NPM registry', () => {
|
|||||||
const env = Object.assign(process.env, {PORT:'0'})
|
const env = Object.assign(process.env, {PORT:'0'})
|
||||||
const res = await exec (resolve(cwd, '.registry/server.js'), {cwd, stdio: 'pipe', env})
|
const res = await exec (resolve(cwd, '.registry/server.js'), {cwd, stdio: 'pipe', env})
|
||||||
registry = res.cp
|
registry = res.cp
|
||||||
axios = require('axios').create ({ baseURL: res.url, validateStatus: (status)=>status<500 })
|
axios = require('axios').default.create ({ baseURL: res.url, validateStatus: (status)=>status<500 })
|
||||||
})
|
})
|
||||||
|
|
||||||
after(done => { registry.once('exit',done); registry.kill() })
|
after(() => { registry.kill() })
|
||||||
|
|
||||||
for (const mod of ['bookshop', 'data-viewer', 'fiori','orders','reviews']) {
|
for (const mod of ['bookshop', 'data-viewer', 'fiori','orders','reviews']) {
|
||||||
it(`should serve ${mod}`, async () => {
|
it(`should serve ${mod}`, async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user